From 1e71815b009e272087480d53c551112262e5e556 Mon Sep 17 00:00:00 2001
From: BankaiNoJutsu <lbegert@gmail.com>
Date: Tue, 18 Dec 2018 08:32:04 +0100
Subject: [PATCH] added squares and triangles themes

---
 Gruntfile.js                                  |   68 +-
 themes/squares/client/src/css/.directory      |    4 +
 .../client/src/css/00-bootstrap.min.css       | 5768 +++++++++++++++++
 themes/squares/client/src/css/01-main.css     |   77 +
 themes/squares/client/src/css/02-login.css    |  136 +
 themes/squares/client/src/css/03-errors.css   |   12 +
 .../client/src/css/03-password-reset-form.css |    4 +
 .../src/css/03-password-reset-request.css     |    4 +
 .../client/src/css/03-totp-register.css       |   22 +
 .../client/src/css/03-u2f-register.css        |    5 +
 .../squares/client/src/img/LargeTriangles.svg |    1 +
 .../client/src/img/RandomizedPattern.svg      |    1 +
 themes/squares/client/src/img/background.jpg  |  Bin 0 -> 587 bytes
 themes/squares/client/src/img/background.svg  |    5 +
 themes/squares/client/src/img/icon.png        |  Bin 0 -> 1461 bytes
 themes/squares/client/src/img/mail.png        |  Bin 0 -> 3545 bytes
 .../client/src/img/matrix_circle_128x128.png  |  Bin 0 -> 35750 bytes
 .../client/src/img/notifications/.directory   |    4 +
 .../client/src/img/notifications/error.png    |  Bin 0 -> 863 bytes
 .../client/src/img/notifications/info.png     |  Bin 0 -> 732 bytes
 .../client/src/img/notifications/success.png  |  Bin 0 -> 931 bytes
 .../client/src/img/notifications/warning.png  |  Bin 0 -> 580 bytes
 themes/squares/client/src/img/padlock.png     |  Bin 0 -> 3265 bytes
 .../squares/client/src/img/password_white.png |  Bin 0 -> 3858 bytes
 themes/squares/client/src/img/pendrive.png    |  Bin 0 -> 6721 bytes
 themes/squares/client/src/img/sharingan.png   |  Bin 0 -> 9213 bytes
 .../squares/client/src/img/stores/.directory  |    4 +
 .../src/img/stores/applestore-badge.svg       |  129 +
 .../src/img/stores/googleplay-badge.svg       |  429 ++
 themes/squares/client/src/img/success.png     |  Bin 0 -> 3147 bytes
 themes/squares/client/src/img/user.png        |  Bin 0 -> 2933 bytes
 themes/squares/client/src/img/warning.png     |  Bin 0 -> 4038 bytes
 themes/squares/client/src/index.ts            |   34 +
 themes/squares/client/src/lib/GetPromised.ts  |   14 +
 themes/squares/client/src/lib/INotifier.ts    |   14 +
 themes/squares/client/src/lib/Notifier.ts     |   83 +
 .../src/lib/QueryParametersRetriever.ts       |   12 +
 themes/squares/client/src/lib/SafeRedirect.ts |   10 +
 .../lib/firstfactor/FirstFactorValidator.ts   |   46 +
 .../client/src/lib/firstfactor/UISelectors.ts |    5 +
 .../client/src/lib/firstfactor/index.ts       |   49 +
 .../src/lib/reset-password/constants.ts       |    2 +
 .../lib/reset-password/reset-password-form.ts |   57 +
 .../reset-password/reset-password-request.ts  |   56 +
 .../src/lib/secondfactor/TOTPValidator.ts     |   28 +
 .../src/lib/secondfactor/U2FValidator.ts      |   42 +
 .../client/src/lib/secondfactor/constants.ts  |    3 +
 .../client/src/lib/secondfactor/index.ts      |   59 +
 .../src/lib/totp-register/totp-register.ts    |   11 +
 .../src/lib/totp-register/ui-selector.ts      |    2 +
 .../src/lib/u2f-register/u2f-register.ts      |   56 +
 .../client/src/thirdparties/qrcode.min.js     |    1 +
 .../client/src/thirdparties/u2f-api.js        |  749 +++
 themes/squares/client/test/Notifier.test.ts   |   71 +
 .../firstfactor/FirstFactorValidator.test.ts  |   44 +
 .../squares/client/test/mocks/NotifierStub.ts |   33 +
 themes/squares/client/test/mocks/jquery.ts    |   59 +
 themes/squares/client/test/mocks/u2f-api.ts   |   14 +
 .../test/secondfactor/TOTPValidator.test.ts   |   37 +
 .../test/totp-register/totp-register.test.ts  |   31 +
 themes/squares/client/tsconfig.json           |   24 +
 themes/squares/client/tslint.json             |   60 +
 themes/squares/server/.directory              |    4 +
 themes/squares/server/src/index.ts            |   28 +
 themes/squares/server/src/lib/.directory      |    4 +
 .../src/lib/AuthenticationSessionHandler.ts   |   45 +
 themes/squares/server/src/lib/ErrorReplies.ts |   49 +
 themes/squares/server/src/lib/Exceptions.ts   |   88 +
 .../server/src/lib/FirstFactorValidator.ts    |   20 +
 .../src/lib/IdentityCheckMiddleware.spec.ts   |  176 +
 .../server/src/lib/IdentityCheckMiddleware.ts |  138 +
 .../lib/IdentityCheckPreValidationTemplate.ts |    3 +
 .../server/src/lib/IdentityValidable.ts       |   19 +
 .../src/lib/IdentityValidableStub.spec.ts     |   52 +
 themes/squares/server/src/lib/Server.spec.ts  |   81 +
 themes/squares/server/src/lib/Server.ts       |   93 +
 .../squares/server/src/lib/ServerVariables.ts |   21 +
 .../src/lib/ServerVariablesInitializer.ts     |  116 +
 .../lib/ServerVariablesMockBuilder.spec.ts    |   87 +
 .../server/src/lib/authentication/Level.ts    |    5 +
 .../backends/GroupsAndEmails.ts               |    5 +
 .../authentication/backends/IUsersDatabase.ts |   10 +
 .../backends/IUsersDatabaseStub.spec.ts       |   35 +
 .../backends/file/FileUsersDatabase.spec.ts   |  224 +
 .../backends/file/FileUsersDatabase.ts        |  182 +
 .../backends/file/ReadWriteQueue.ts           |   60 +
 .../authentication/backends/ldap/ISession.ts  |   12 +
 .../backends/ldap/ISessionFactory.ts          |    6 +
 .../backends/ldap/LdapUsersDatabase.spec.ts   |  386 ++
 .../backends/ldap/LdapUsersDatabase.ts        |  107 +
 .../backends/ldap/SafeSession.spec.ts         |   76 +
 .../backends/ldap/SafeSession.ts              |   62 +
 .../backends/ldap/Sanitizer.spec.ts           |   25 +
 .../authentication/backends/ldap/Sanitizer.ts |   25 +
 .../backends/ldap/Session.spec.ts             |  127 +
 .../authentication/backends/ldap/Session.ts   |  156 +
 .../backends/ldap/SessionFactory.ts           |   37 +
 .../backends/ldap/SessionFactoryStub.spec.ts  |   16 +
 .../backends/ldap/SessionStub.spec.ts         |   46 +
 .../backends/ldap/connector/Connector.ts      |   69 +
 .../ldap/connector/ConnectorFactory.ts        |   18 +
 .../connector/ConnectorFactoryStub.spec.ts    |   17 +
 .../ldap/connector/ConnectorStub.spec.ts      |   34 +
 .../backends/ldap/connector/IConnector.ts     |    9 +
 .../ldap/connector/IConnectorFactory.ts       |    5 +
 .../lib/authentication/totp/ITotpHandler.ts   |    6 +
 .../authentication/totp/TotpHandler.spec.ts   |   39 +
 .../lib/authentication/totp/TotpHandler.ts    |   36 +
 .../totp/TotpHandlerStub.spec.ts              |   22 +
 .../src/lib/authentication/u2f/IU2fHandler.ts |    9 +
 .../src/lib/authentication/u2f/U2fHandler.ts  |   24 +
 .../authentication/u2f/U2fHandlerStub.spec.ts |   31 +
 .../src/lib/authorization/Authorizer.spec.ts  |  372 ++
 .../src/lib/authorization/Authorizer.ts       |   85 +
 .../lib/authorization/AuthorizerStub.spec.ts  |   17 +
 .../src/lib/authorization/IAuthorizer.ts      |    7 +
 .../server/src/lib/authorization/Level.ts     |    6 +
 .../authorization/MultipleDomainMatcher.ts    |   12 +
 .../server/src/lib/authorization/Object.ts    |    5 +
 .../server/src/lib/authorization/Subject.ts   |    5 +
 .../configuration/ConfigurationParser.spec.ts |  171 +
 .../lib/configuration/ConfigurationParser.ts  |   39 +
 .../SessionConfigurationBuilder.spec.ts       |  149 +
 .../SessionConfigurationBuilder.ts            |   52 +
 .../schema/AclConfiguration.spec.ts           |   34 +
 .../configuration/schema/AclConfiguration.ts  |   41 +
 ...AuthenticationBackendConfiguration.spec.ts |   11 +
 .../AuthenticationBackendConfiguration.ts     |   25 +
 .../lib/configuration/schema/Configuration.ts |   68 +
 .../schema/FileUsersDatabaseConfiguration.ts  |    4 +
 .../schema/LdapConfiguration.spec.ts          |   25 +
 .../configuration/schema/LdapConfiguration.ts |   40 +
 .../schema/NotifierConfiguration.spec.ts      |   40 +
 .../schema/NotifierConfiguration.ts           |   45 +
 .../schema/RegulationConfiguration.spec.ts    |   13 +
 .../schema/RegulationConfiguration.ts         |   23 +
 .../schema/SessionConfiguration.spec.ts       |   16 +
 .../schema/SessionConfiguration.ts            |   32 +
 .../schema/StorageConfiguration.spec.ts       |   15 +
 .../schema/StorageConfiguration.ts            |   30 +
 .../configuration/schema/TotpConfiguration.ts |   13 +
 .../schema/UserDatabaseConfiguration.ts       |    9 +
 .../lib/connectors/mongo/IMongoClient.d.ts    |    6 +
 .../lib/connectors/mongo/MongoClient.spec.ts  |  119 +
 .../src/lib/connectors/mongo/MongoClient.ts   |   76 +
 .../connectors/mongo/MongoClientStub.spec.ts  |   16 +
 .../server/src/lib/logging/GlobalLogger.ts    |   34 +
 .../src/lib/logging/GlobalLoggerStub.spec.ts  |   38 +
 .../server/src/lib/logging/IGlobalLogger.ts   |    5 +
 .../server/src/lib/logging/IRequestLogger.ts  |    7 +
 .../server/src/lib/logging/RequestLogger.ts   |   45 +
 .../src/lib/logging/RequestLoggerStub.spec.ts |   38 +
 .../lib/notifiers/AbstractEmailNotifier.ts    |   23 +
 .../src/lib/notifiers/EmailNotifier.spec.ts   |   54 +
 .../server/src/lib/notifiers/EmailNotifier.ts |   27 +
 .../src/lib/notifiers/FileSystemNotifier.ts   |   22 +
 .../server/src/lib/notifiers/IMailSender.ts   |    6 +
 .../src/lib/notifiers/IMailSenderBuilder.ts   |    7 +
 .../server/src/lib/notifiers/INotifier.ts     |    5 +
 .../server/src/lib/notifiers/MailSender.ts    |   42 +
 .../lib/notifiers/MailSenderBuilder.spec.ts   |   67 +
 .../src/lib/notifiers/MailSenderBuilder.ts    |   42 +
 .../notifiers/MailSenderBuilderStub.spec.ts   |   25 +
 .../src/lib/notifiers/MailSenderStub.spec.ts  |   16 +
 .../src/lib/notifiers/NotifierFactory.spec.ts |   42 +
 .../src/lib/notifiers/NotifierFactory.ts      |   33 +
 .../src/lib/notifiers/NotifierStub.spec.ts    |   16 +
 .../server/src/lib/notifiers/SmtpNotifier.ts  |   30 +
 .../server/src/lib/regulation/IRegulator.ts   |    6 +
 .../src/lib/regulation/Regulator.spec.ts      |  186 +
 .../server/src/lib/regulation/Regulator.ts    |   55 +
 .../src/lib/regulation/RegulatorStub.spec.ts  |   22 +
 .../src/lib/routes/error/401/get.spec.ts      |   61 +
 .../server/src/lib/routes/error/401/get.ts    |   15 +
 .../src/lib/routes/error/403/get.spec.ts      |   61 +
 .../server/src/lib/routes/error/403/get.ts    |   15 +
 .../src/lib/routes/error/404/get.spec.ts      |   19 +
 .../server/src/lib/routes/error/404/get.ts    |    8 +
 .../server/src/lib/routes/error/redirector.ts |   13 +
 .../server/src/lib/routes/firstfactor/get.ts  |   72 +
 .../src/lib/routes/firstfactor/post.spec.ts   |  136 +
 .../server/src/lib/routes/firstfactor/post.ts |  101 +
 .../server/src/lib/routes/loggedin/get.ts     |   23 +
 .../server/src/lib/routes/logout/get.ts       |   20 +
 .../lib/routes/password-reset/constants.ts    |    2 +
 .../routes/password-reset/form/post.spec.ts   |  122 +
 .../lib/routes/password-reset/form/post.ts    |   50 +
 .../identity/PasswordResetHandler.spec.ts     |   92 +
 .../identity/PasswordResetHandler.ts          |   69 +
 .../lib/routes/password-reset/request/get.ts  |   13 +
 .../src/lib/routes/secondfactor/get.spec.ts   |   44 +
 .../server/src/lib/routes/secondfactor/get.ts |   28 +
 .../lib/routes/secondfactor/redirect.spec.ts  |   41 +
 .../src/lib/routes/secondfactor/redirect.ts   |   30 +
 .../lib/routes/secondfactor/totp/constants.ts |    4 +
 .../totp/identity/RegistrationHandler.spec.ts |  116 +
 .../totp/identity/RegistrationHandler.ts      |  112 +
 .../secondfactor/totp/sign/post.spec.ts       |   76 +
 .../lib/routes/secondfactor/totp/sign/post.ts |   42 +
 .../lib/routes/secondfactor/u2f/U2FCommon.ts  |   11 +
 .../u2f/identity/RegistrationHandler.spec.ts  |   96 +
 .../u2f/identity/RegistrationHandler.ts       |   73 +
 .../secondfactor/u2f/register/post.spec.ts    |  146 +
 .../routes/secondfactor/u2f/register/post.ts  |   64 +
 .../u2f/register_request/get.spec.ts          |   86 +
 .../secondfactor/u2f/register_request/get.ts  |   43 +
 .../routes/secondfactor/u2f/sign/post.spec.ts |  101 +
 .../lib/routes/secondfactor/u2f/sign/post.ts  |   57 +
 .../secondfactor/u2f/sign_request/get.spec.ts |   68 +
 .../secondfactor/u2f/sign_request/get.ts      |   42 +
 .../src/lib/routes/verify/access_control.ts   |   51 +
 .../server/src/lib/routes/verify/get.spec.ts  |  320 +
 .../server/src/lib/routes/verify/get.ts       |   91 +
 .../src/lib/routes/verify/get_basic_auth.ts   |   55 +
 .../lib/routes/verify/get_session_cookie.ts   |   78 +
 .../storage/AuthenticationTraceDocument.d.ts  |    6 +
 .../lib/storage/CollectionFactoryFactory.ts   |   15 +
 .../lib/storage/CollectionFactoryStub.spec.ts |   16 +
 .../src/lib/storage/CollectionStub.spec.ts    |   39 +
 .../server/src/lib/storage/ICollection.d.ts   |   11 +
 .../src/lib/storage/ICollectionFactory.d.ts   |    6 +
 .../src/lib/storage/IUserDataStore.d.ts       |   21 +
 .../storage/IdentityValidationDocument.d.ts   |    7 +
 .../src/lib/storage/TOTPSecretDocument.d.ts   |    6 +
 .../lib/storage/U2FRegistrationDocument.d.ts  |    8 +
 .../src/lib/storage/UserDataStore.spec.ts     |  264 +
 .../server/src/lib/storage/UserDataStore.ts   |  143 +
 .../src/lib/storage/UserDataStoreStub.spec.ts |   64 +
 .../lib/storage/mongo/MongoCollection.spec.ts |  110 +
 .../src/lib/storage/mongo/MongoCollection.ts  |   50 +
 .../mongo/MongoCollectionFactory.spec.ts      |   21 +
 .../storage/mongo/MongoCollectionFactory.ts   |   19 +
 .../lib/storage/nedb/NedbCollection.spec.ts   |  136 +
 .../src/lib/storage/nedb/NedbCollection.ts    |   47 +
 .../nedb/NedbCollectionFactory.spec.ts        |   16 +
 .../lib/storage/nedb/NedbCollectionFactory.ts |   28 +
 .../server/src/lib/stubs/express.spec.ts      |  103 +
 .../server/src/lib/stubs/ldapjs.spec.ts       |   50 +
 .../server/src/lib/stubs/speakeasy.spec.ts    |    7 +
 .../squares/server/src/lib/stubs/u2f.spec.ts  |   16 +
 .../src/lib/utils/HashGenerator.spec.ts       |   18 +
 .../server/src/lib/utils/HashGenerator.ts     |   23 +
 .../server/src/lib/utils/ObjectCloner.ts      |    6 +
 .../src/lib/utils/SafeRedirection.spec.ts     |   33 +
 .../server/src/lib/utils/SafeRedirection.ts   |   22 +
 .../src/lib/utils/URLDecomposer.spec.ts       |   46 +
 .../server/src/lib/utils/URLDecomposer.ts     |   15 +
 .../server/src/lib/web_server/Configurator.ts |   47 +
 .../server/src/lib/web_server/RestApi.ts      |  125 +
 .../RequireValidatedFirstFactor.ts            |   27 +
 .../middlewares/WithHeadersLogged.ts          |   12 +
 .../server/src/resources/email-template.ejs   |  254 +
 .../server/src/views/already-logged-in.pug    |   14 +
 .../server/src/views/errors/.directory        |    4 +
 .../squares/server/src/views/errors/401.pug   |   16 +
 .../squares/server/src/views/errors/403.pug   |   16 +
 .../squares/server/src/views/errors/404.pug   |   11 +
 .../squares/server/src/views/firstfactor.pug  |   23 +
 .../server/src/views/layout/layout.pug        |   28 +
 .../src/views/need-identity-validation.pug    |   12 +
 .../server/src/views/password-reset-form.pug  |   18 +
 .../src/views/password-reset-request.pug      |   18 +
 .../squares/server/src/views/secondfactor.pug |   31 +
 .../server/src/views/totp-register.pug        |   25 +
 .../squares/server/src/views/u2f-register.pug |   12 +
 themes/squares/server/test/requests.ts        |   94 +
 themes/squares/server/tsconfig.json           |   21 +
 themes/squares/server/tslint.json             |   60 +
 themes/squares/server/types/.directory        |    4 +
 .../server/types/AuthenticationSession.ts     |   18 +
 themes/squares/server/types/Dependencies.ts   |   29 +
 themes/squares/server/types/Identity.ts       |    6 +
 themes/squares/server/types/TOTPSecret.ts     |   11 +
 .../squares/server/types/U2FRegistration.ts   |    5 +
 themes/squares/server/types/dovehash.d.ts     |    4 +
 themes/squares/server/types/speakeasy.d.ts    |   96 +
 themes/triangles/client/src/.directory        |    4 +
 themes/triangles/client/src/css/.directory    |    4 +
 .../client/src/css/00-bootstrap.min.css       | 5768 +++++++++++++++++
 themes/triangles/client/src/css/01-main.css   |   77 +
 themes/triangles/client/src/css/02-login.css  |  136 +
 themes/triangles/client/src/css/03-errors.css |   12 +
 .../client/src/css/03-password-reset-form.css |    4 +
 .../src/css/03-password-reset-request.css     |    4 +
 .../client/src/css/03-totp-register.css       |   22 +
 .../client/src/css/03-u2f-register.css        |    5 +
 .../client/src/img/LargeTriangles.svg         |    1 +
 .../triangles/client/src/img/background.jpg   |  Bin 0 -> 587 bytes
 themes/triangles/client/src/img/icon.png      |  Bin 0 -> 1461 bytes
 themes/triangles/client/src/img/mail.png      |  Bin 0 -> 3545 bytes
 .../client/src/img/matrix_circle_128x128.png  |  Bin 0 -> 35750 bytes
 .../client/src/img/notifications/.directory   |    4 +
 .../client/src/img/notifications/error.png    |  Bin 0 -> 863 bytes
 .../client/src/img/notifications/info.png     |  Bin 0 -> 732 bytes
 .../client/src/img/notifications/success.png  |  Bin 0 -> 931 bytes
 .../client/src/img/notifications/warning.png  |  Bin 0 -> 580 bytes
 themes/triangles/client/src/img/padlock.png   |  Bin 0 -> 3265 bytes
 .../client/src/img/password_white.png         |  Bin 0 -> 3858 bytes
 themes/triangles/client/src/img/pendrive.png  |  Bin 0 -> 6721 bytes
 themes/triangles/client/src/img/sharingan.png |  Bin 0 -> 9213 bytes
 .../client/src/img/stores/.directory          |    4 +
 .../src/img/stores/applestore-badge.svg       |  129 +
 .../src/img/stores/googleplay-badge.svg       |  429 ++
 themes/triangles/client/src/img/success.png   |  Bin 0 -> 3147 bytes
 themes/triangles/client/src/img/user.png      |  Bin 0 -> 2933 bytes
 themes/triangles/client/src/img/warning.png   |  Bin 0 -> 4038 bytes
 themes/triangles/client/src/index.ts          |   34 +
 .../triangles/client/src/lib/GetPromised.ts   |   14 +
 themes/triangles/client/src/lib/INotifier.ts  |   14 +
 themes/triangles/client/src/lib/Notifier.ts   |   83 +
 .../src/lib/QueryParametersRetriever.ts       |   12 +
 .../triangles/client/src/lib/SafeRedirect.ts  |   10 +
 .../lib/firstfactor/FirstFactorValidator.ts   |   46 +
 .../client/src/lib/firstfactor/UISelectors.ts |    5 +
 .../client/src/lib/firstfactor/index.ts       |   49 +
 .../src/lib/reset-password/constants.ts       |    2 +
 .../lib/reset-password/reset-password-form.ts |   57 +
 .../reset-password/reset-password-request.ts  |   56 +
 .../src/lib/secondfactor/TOTPValidator.ts     |   28 +
 .../src/lib/secondfactor/U2FValidator.ts      |   42 +
 .../client/src/lib/secondfactor/constants.ts  |    3 +
 .../client/src/lib/secondfactor/index.ts      |   59 +
 .../src/lib/totp-register/totp-register.ts    |   11 +
 .../src/lib/totp-register/ui-selector.ts      |    2 +
 .../src/lib/u2f-register/u2f-register.ts      |   56 +
 .../client/src/thirdparties/qrcode.min.js     |    1 +
 .../client/src/thirdparties/u2f-api.js        |  749 +++
 themes/triangles/client/test/Notifier.test.ts |   71 +
 .../firstfactor/FirstFactorValidator.test.ts  |   44 +
 .../client/test/mocks/NotifierStub.ts         |   33 +
 themes/triangles/client/test/mocks/jquery.ts  |   59 +
 themes/triangles/client/test/mocks/u2f-api.ts |   14 +
 .../test/secondfactor/TOTPValidator.test.ts   |   37 +
 .../test/totp-register/totp-register.test.ts  |   31 +
 themes/triangles/client/tsconfig.json         |   24 +
 themes/triangles/client/tslint.json           |   60 +
 themes/triangles/server/.directory            |    4 +
 themes/triangles/server/src/index.ts          |   28 +
 themes/triangles/server/src/lib/.directory    |    4 +
 .../src/lib/AuthenticationSessionHandler.ts   |   45 +
 .../triangles/server/src/lib/ErrorReplies.ts  |   49 +
 themes/triangles/server/src/lib/Exceptions.ts |   88 +
 .../server/src/lib/FirstFactorValidator.ts    |   20 +
 .../src/lib/IdentityCheckMiddleware.spec.ts   |  176 +
 .../server/src/lib/IdentityCheckMiddleware.ts |  138 +
 .../lib/IdentityCheckPreValidationTemplate.ts |    3 +
 .../server/src/lib/IdentityValidable.ts       |   19 +
 .../src/lib/IdentityValidableStub.spec.ts     |   52 +
 .../triangles/server/src/lib/Server.spec.ts   |   81 +
 themes/triangles/server/src/lib/Server.ts     |   93 +
 .../server/src/lib/ServerVariables.ts         |   21 +
 .../src/lib/ServerVariablesInitializer.ts     |  116 +
 .../lib/ServerVariablesMockBuilder.spec.ts    |   87 +
 .../server/src/lib/authentication/Level.ts    |    5 +
 .../backends/GroupsAndEmails.ts               |    5 +
 .../authentication/backends/IUsersDatabase.ts |   10 +
 .../backends/IUsersDatabaseStub.spec.ts       |   35 +
 .../backends/file/FileUsersDatabase.spec.ts   |  224 +
 .../backends/file/FileUsersDatabase.ts        |  182 +
 .../backends/file/ReadWriteQueue.ts           |   60 +
 .../authentication/backends/ldap/ISession.ts  |   12 +
 .../backends/ldap/ISessionFactory.ts          |    6 +
 .../backends/ldap/LdapUsersDatabase.spec.ts   |  386 ++
 .../backends/ldap/LdapUsersDatabase.ts        |  107 +
 .../backends/ldap/SafeSession.spec.ts         |   76 +
 .../backends/ldap/SafeSession.ts              |   62 +
 .../backends/ldap/Sanitizer.spec.ts           |   25 +
 .../authentication/backends/ldap/Sanitizer.ts |   25 +
 .../backends/ldap/Session.spec.ts             |  127 +
 .../authentication/backends/ldap/Session.ts   |  156 +
 .../backends/ldap/SessionFactory.ts           |   37 +
 .../backends/ldap/SessionFactoryStub.spec.ts  |   16 +
 .../backends/ldap/SessionStub.spec.ts         |   46 +
 .../backends/ldap/connector/Connector.ts      |   69 +
 .../ldap/connector/ConnectorFactory.ts        |   18 +
 .../connector/ConnectorFactoryStub.spec.ts    |   17 +
 .../ldap/connector/ConnectorStub.spec.ts      |   34 +
 .../backends/ldap/connector/IConnector.ts     |    9 +
 .../ldap/connector/IConnectorFactory.ts       |    5 +
 .../lib/authentication/totp/ITotpHandler.ts   |    6 +
 .../authentication/totp/TotpHandler.spec.ts   |   39 +
 .../lib/authentication/totp/TotpHandler.ts    |   36 +
 .../totp/TotpHandlerStub.spec.ts              |   22 +
 .../src/lib/authentication/u2f/IU2fHandler.ts |    9 +
 .../src/lib/authentication/u2f/U2fHandler.ts  |   24 +
 .../authentication/u2f/U2fHandlerStub.spec.ts |   31 +
 .../src/lib/authorization/Authorizer.spec.ts  |  372 ++
 .../src/lib/authorization/Authorizer.ts       |   85 +
 .../lib/authorization/AuthorizerStub.spec.ts  |   17 +
 .../src/lib/authorization/IAuthorizer.ts      |    7 +
 .../server/src/lib/authorization/Level.ts     |    6 +
 .../authorization/MultipleDomainMatcher.ts    |   12 +
 .../server/src/lib/authorization/Object.ts    |    5 +
 .../server/src/lib/authorization/Subject.ts   |    5 +
 .../configuration/ConfigurationParser.spec.ts |  171 +
 .../lib/configuration/ConfigurationParser.ts  |   39 +
 .../SessionConfigurationBuilder.spec.ts       |  149 +
 .../SessionConfigurationBuilder.ts            |   52 +
 .../schema/AclConfiguration.spec.ts           |   34 +
 .../configuration/schema/AclConfiguration.ts  |   41 +
 ...AuthenticationBackendConfiguration.spec.ts |   11 +
 .../AuthenticationBackendConfiguration.ts     |   25 +
 .../lib/configuration/schema/Configuration.ts |   68 +
 .../schema/FileUsersDatabaseConfiguration.ts  |    4 +
 .../schema/LdapConfiguration.spec.ts          |   25 +
 .../configuration/schema/LdapConfiguration.ts |   40 +
 .../schema/NotifierConfiguration.spec.ts      |   40 +
 .../schema/NotifierConfiguration.ts           |   45 +
 .../schema/RegulationConfiguration.spec.ts    |   13 +
 .../schema/RegulationConfiguration.ts         |   23 +
 .../schema/SessionConfiguration.spec.ts       |   16 +
 .../schema/SessionConfiguration.ts            |   32 +
 .../schema/StorageConfiguration.spec.ts       |   15 +
 .../schema/StorageConfiguration.ts            |   30 +
 .../configuration/schema/TotpConfiguration.ts |   13 +
 .../schema/UserDatabaseConfiguration.ts       |    9 +
 .../lib/connectors/mongo/IMongoClient.d.ts    |    6 +
 .../lib/connectors/mongo/MongoClient.spec.ts  |  119 +
 .../src/lib/connectors/mongo/MongoClient.ts   |   76 +
 .../connectors/mongo/MongoClientStub.spec.ts  |   16 +
 .../server/src/lib/logging/GlobalLogger.ts    |   34 +
 .../src/lib/logging/GlobalLoggerStub.spec.ts  |   38 +
 .../server/src/lib/logging/IGlobalLogger.ts   |    5 +
 .../server/src/lib/logging/IRequestLogger.ts  |    7 +
 .../server/src/lib/logging/RequestLogger.ts   |   45 +
 .../src/lib/logging/RequestLoggerStub.spec.ts |   38 +
 .../lib/notifiers/AbstractEmailNotifier.ts    |   23 +
 .../src/lib/notifiers/EmailNotifier.spec.ts   |   54 +
 .../server/src/lib/notifiers/EmailNotifier.ts |   27 +
 .../src/lib/notifiers/FileSystemNotifier.ts   |   22 +
 .../server/src/lib/notifiers/IMailSender.ts   |    6 +
 .../src/lib/notifiers/IMailSenderBuilder.ts   |    7 +
 .../server/src/lib/notifiers/INotifier.ts     |    5 +
 .../server/src/lib/notifiers/MailSender.ts    |   42 +
 .../lib/notifiers/MailSenderBuilder.spec.ts   |   67 +
 .../src/lib/notifiers/MailSenderBuilder.ts    |   42 +
 .../notifiers/MailSenderBuilderStub.spec.ts   |   25 +
 .../src/lib/notifiers/MailSenderStub.spec.ts  |   16 +
 .../src/lib/notifiers/NotifierFactory.spec.ts |   42 +
 .../src/lib/notifiers/NotifierFactory.ts      |   33 +
 .../src/lib/notifiers/NotifierStub.spec.ts    |   16 +
 .../server/src/lib/notifiers/SmtpNotifier.ts  |   30 +
 .../server/src/lib/regulation/IRegulator.ts   |    6 +
 .../src/lib/regulation/Regulator.spec.ts      |  186 +
 .../server/src/lib/regulation/Regulator.ts    |   55 +
 .../src/lib/regulation/RegulatorStub.spec.ts  |   22 +
 .../src/lib/routes/error/401/get.spec.ts      |   61 +
 .../server/src/lib/routes/error/401/get.ts    |   15 +
 .../src/lib/routes/error/403/get.spec.ts      |   61 +
 .../server/src/lib/routes/error/403/get.ts    |   15 +
 .../src/lib/routes/error/404/get.spec.ts      |   19 +
 .../server/src/lib/routes/error/404/get.ts    |    8 +
 .../server/src/lib/routes/error/redirector.ts |   13 +
 .../server/src/lib/routes/firstfactor/get.ts  |   72 +
 .../src/lib/routes/firstfactor/post.spec.ts   |  136 +
 .../server/src/lib/routes/firstfactor/post.ts |  101 +
 .../server/src/lib/routes/loggedin/get.ts     |   23 +
 .../server/src/lib/routes/logout/get.ts       |   20 +
 .../lib/routes/password-reset/constants.ts    |    2 +
 .../routes/password-reset/form/post.spec.ts   |  122 +
 .../lib/routes/password-reset/form/post.ts    |   50 +
 .../identity/PasswordResetHandler.spec.ts     |   92 +
 .../identity/PasswordResetHandler.ts          |   69 +
 .../lib/routes/password-reset/request/get.ts  |   13 +
 .../src/lib/routes/secondfactor/get.spec.ts   |   44 +
 .../server/src/lib/routes/secondfactor/get.ts |   28 +
 .../lib/routes/secondfactor/redirect.spec.ts  |   41 +
 .../src/lib/routes/secondfactor/redirect.ts   |   30 +
 .../lib/routes/secondfactor/totp/constants.ts |    4 +
 .../totp/identity/RegistrationHandler.spec.ts |  116 +
 .../totp/identity/RegistrationHandler.ts      |  112 +
 .../secondfactor/totp/sign/post.spec.ts       |   76 +
 .../lib/routes/secondfactor/totp/sign/post.ts |   42 +
 .../lib/routes/secondfactor/u2f/U2FCommon.ts  |   11 +
 .../u2f/identity/RegistrationHandler.spec.ts  |   96 +
 .../u2f/identity/RegistrationHandler.ts       |   73 +
 .../secondfactor/u2f/register/post.spec.ts    |  146 +
 .../routes/secondfactor/u2f/register/post.ts  |   64 +
 .../u2f/register_request/get.spec.ts          |   86 +
 .../secondfactor/u2f/register_request/get.ts  |   43 +
 .../routes/secondfactor/u2f/sign/post.spec.ts |  101 +
 .../lib/routes/secondfactor/u2f/sign/post.ts  |   57 +
 .../secondfactor/u2f/sign_request/get.spec.ts |   68 +
 .../secondfactor/u2f/sign_request/get.ts      |   42 +
 .../src/lib/routes/verify/access_control.ts   |   51 +
 .../server/src/lib/routes/verify/get.spec.ts  |  320 +
 .../server/src/lib/routes/verify/get.ts       |   91 +
 .../src/lib/routes/verify/get_basic_auth.ts   |   55 +
 .../lib/routes/verify/get_session_cookie.ts   |   78 +
 .../storage/AuthenticationTraceDocument.d.ts  |    6 +
 .../lib/storage/CollectionFactoryFactory.ts   |   15 +
 .../lib/storage/CollectionFactoryStub.spec.ts |   16 +
 .../src/lib/storage/CollectionStub.spec.ts    |   39 +
 .../server/src/lib/storage/ICollection.d.ts   |   11 +
 .../src/lib/storage/ICollectionFactory.d.ts   |    6 +
 .../src/lib/storage/IUserDataStore.d.ts       |   21 +
 .../storage/IdentityValidationDocument.d.ts   |    7 +
 .../src/lib/storage/TOTPSecretDocument.d.ts   |    6 +
 .../lib/storage/U2FRegistrationDocument.d.ts  |    8 +
 .../src/lib/storage/UserDataStore.spec.ts     |  264 +
 .../server/src/lib/storage/UserDataStore.ts   |  143 +
 .../src/lib/storage/UserDataStoreStub.spec.ts |   64 +
 .../lib/storage/mongo/MongoCollection.spec.ts |  110 +
 .../src/lib/storage/mongo/MongoCollection.ts  |   50 +
 .../mongo/MongoCollectionFactory.spec.ts      |   21 +
 .../storage/mongo/MongoCollectionFactory.ts   |   19 +
 .../lib/storage/nedb/NedbCollection.spec.ts   |  136 +
 .../src/lib/storage/nedb/NedbCollection.ts    |   47 +
 .../nedb/NedbCollectionFactory.spec.ts        |   16 +
 .../lib/storage/nedb/NedbCollectionFactory.ts |   28 +
 .../server/src/lib/stubs/express.spec.ts      |  103 +
 .../server/src/lib/stubs/ldapjs.spec.ts       |   50 +
 .../server/src/lib/stubs/speakeasy.spec.ts    |    7 +
 .../server/src/lib/stubs/u2f.spec.ts          |   16 +
 .../src/lib/utils/HashGenerator.spec.ts       |   18 +
 .../server/src/lib/utils/HashGenerator.ts     |   23 +
 .../server/src/lib/utils/ObjectCloner.ts      |    6 +
 .../src/lib/utils/SafeRedirection.spec.ts     |   33 +
 .../server/src/lib/utils/SafeRedirection.ts   |   22 +
 .../src/lib/utils/URLDecomposer.spec.ts       |   46 +
 .../server/src/lib/utils/URLDecomposer.ts     |   15 +
 .../server/src/lib/web_server/Configurator.ts |   47 +
 .../server/src/lib/web_server/RestApi.ts      |  125 +
 .../RequireValidatedFirstFactor.ts            |   27 +
 .../middlewares/WithHeadersLogged.ts          |   12 +
 .../server/src/resources/email-template.ejs   |  254 +
 .../server/src/views/already-logged-in.pug    |   14 +
 .../server/src/views/errors/.directory        |    4 +
 .../triangles/server/src/views/errors/401.pug |   16 +
 .../triangles/server/src/views/errors/403.pug |   16 +
 .../triangles/server/src/views/errors/404.pug |   11 +
 .../server/src/views/firstfactor.pug          |   23 +
 .../server/src/views/layout/layout.pug        |   28 +
 .../src/views/need-identity-validation.pug    |   12 +
 .../server/src/views/password-reset-form.pug  |   18 +
 .../src/views/password-reset-request.pug      |   18 +
 .../server/src/views/secondfactor.pug         |   31 +
 .../server/src/views/totp-register.pug        |   25 +
 .../server/src/views/u2f-register.pug         |   12 +
 themes/triangles/server/test/requests.ts      |   94 +
 themes/triangles/server/tsconfig.json         |   21 +
 themes/triangles/server/tslint.json           |   60 +
 themes/triangles/server/types/.directory      |    4 +
 .../server/types/AuthenticationSession.ts     |   18 +
 themes/triangles/server/types/Dependencies.ts |   29 +
 themes/triangles/server/types/Identity.ts     |    6 +
 themes/triangles/server/types/TOTPSecret.ts   |   11 +
 .../triangles/server/types/U2FRegistration.ts |    5 +
 themes/triangles/server/types/dovehash.d.ts   |    4 +
 themes/triangles/server/types/speakeasy.d.ts  |   96 +
 550 files changed, 37963 insertions(+), 3 deletions(-)
 create mode 100644 themes/squares/client/src/css/.directory
 create mode 100644 themes/squares/client/src/css/00-bootstrap.min.css
 create mode 100644 themes/squares/client/src/css/01-main.css
 create mode 100644 themes/squares/client/src/css/02-login.css
 create mode 100644 themes/squares/client/src/css/03-errors.css
 create mode 100644 themes/squares/client/src/css/03-password-reset-form.css
 create mode 100644 themes/squares/client/src/css/03-password-reset-request.css
 create mode 100644 themes/squares/client/src/css/03-totp-register.css
 create mode 100644 themes/squares/client/src/css/03-u2f-register.css
 create mode 100644 themes/squares/client/src/img/LargeTriangles.svg
 create mode 100644 themes/squares/client/src/img/RandomizedPattern.svg
 create mode 100644 themes/squares/client/src/img/background.jpg
 create mode 100644 themes/squares/client/src/img/background.svg
 create mode 100644 themes/squares/client/src/img/icon.png
 create mode 100644 themes/squares/client/src/img/mail.png
 create mode 100644 themes/squares/client/src/img/matrix_circle_128x128.png
 create mode 100644 themes/squares/client/src/img/notifications/.directory
 create mode 100644 themes/squares/client/src/img/notifications/error.png
 create mode 100644 themes/squares/client/src/img/notifications/info.png
 create mode 100644 themes/squares/client/src/img/notifications/success.png
 create mode 100644 themes/squares/client/src/img/notifications/warning.png
 create mode 100644 themes/squares/client/src/img/padlock.png
 create mode 100644 themes/squares/client/src/img/password_white.png
 create mode 100644 themes/squares/client/src/img/pendrive.png
 create mode 100644 themes/squares/client/src/img/sharingan.png
 create mode 100644 themes/squares/client/src/img/stores/.directory
 create mode 100644 themes/squares/client/src/img/stores/applestore-badge.svg
 create mode 100644 themes/squares/client/src/img/stores/googleplay-badge.svg
 create mode 100644 themes/squares/client/src/img/success.png
 create mode 100644 themes/squares/client/src/img/user.png
 create mode 100644 themes/squares/client/src/img/warning.png
 create mode 100644 themes/squares/client/src/index.ts
 create mode 100644 themes/squares/client/src/lib/GetPromised.ts
 create mode 100644 themes/squares/client/src/lib/INotifier.ts
 create mode 100644 themes/squares/client/src/lib/Notifier.ts
 create mode 100644 themes/squares/client/src/lib/QueryParametersRetriever.ts
 create mode 100644 themes/squares/client/src/lib/SafeRedirect.ts
 create mode 100644 themes/squares/client/src/lib/firstfactor/FirstFactorValidator.ts
 create mode 100644 themes/squares/client/src/lib/firstfactor/UISelectors.ts
 create mode 100644 themes/squares/client/src/lib/firstfactor/index.ts
 create mode 100644 themes/squares/client/src/lib/reset-password/constants.ts
 create mode 100644 themes/squares/client/src/lib/reset-password/reset-password-form.ts
 create mode 100644 themes/squares/client/src/lib/reset-password/reset-password-request.ts
 create mode 100644 themes/squares/client/src/lib/secondfactor/TOTPValidator.ts
 create mode 100644 themes/squares/client/src/lib/secondfactor/U2FValidator.ts
 create mode 100644 themes/squares/client/src/lib/secondfactor/constants.ts
 create mode 100644 themes/squares/client/src/lib/secondfactor/index.ts
 create mode 100644 themes/squares/client/src/lib/totp-register/totp-register.ts
 create mode 100644 themes/squares/client/src/lib/totp-register/ui-selector.ts
 create mode 100644 themes/squares/client/src/lib/u2f-register/u2f-register.ts
 create mode 100644 themes/squares/client/src/thirdparties/qrcode.min.js
 create mode 100644 themes/squares/client/src/thirdparties/u2f-api.js
 create mode 100644 themes/squares/client/test/Notifier.test.ts
 create mode 100644 themes/squares/client/test/firstfactor/FirstFactorValidator.test.ts
 create mode 100644 themes/squares/client/test/mocks/NotifierStub.ts
 create mode 100644 themes/squares/client/test/mocks/jquery.ts
 create mode 100644 themes/squares/client/test/mocks/u2f-api.ts
 create mode 100644 themes/squares/client/test/secondfactor/TOTPValidator.test.ts
 create mode 100644 themes/squares/client/test/totp-register/totp-register.test.ts
 create mode 100644 themes/squares/client/tsconfig.json
 create mode 100644 themes/squares/client/tslint.json
 create mode 100644 themes/squares/server/.directory
 create mode 100755 themes/squares/server/src/index.ts
 create mode 100644 themes/squares/server/src/lib/.directory
 create mode 100644 themes/squares/server/src/lib/AuthenticationSessionHandler.ts
 create mode 100644 themes/squares/server/src/lib/ErrorReplies.ts
 create mode 100644 themes/squares/server/src/lib/Exceptions.ts
 create mode 100644 themes/squares/server/src/lib/FirstFactorValidator.ts
 create mode 100644 themes/squares/server/src/lib/IdentityCheckMiddleware.spec.ts
 create mode 100644 themes/squares/server/src/lib/IdentityCheckMiddleware.ts
 create mode 100644 themes/squares/server/src/lib/IdentityCheckPreValidationTemplate.ts
 create mode 100644 themes/squares/server/src/lib/IdentityValidable.ts
 create mode 100644 themes/squares/server/src/lib/IdentityValidableStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/Server.spec.ts
 create mode 100644 themes/squares/server/src/lib/Server.ts
 create mode 100644 themes/squares/server/src/lib/ServerVariables.ts
 create mode 100644 themes/squares/server/src/lib/ServerVariablesInitializer.ts
 create mode 100644 themes/squares/server/src/lib/ServerVariablesMockBuilder.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/Level.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/GroupsAndEmails.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/IUsersDatabase.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/IUsersDatabaseStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/file/FileUsersDatabase.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/file/FileUsersDatabase.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/file/ReadWriteQueue.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/ISession.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/ISessionFactory.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/SafeSession.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/SafeSession.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/Sanitizer.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/Sanitizer.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/Session.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/Session.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/SessionFactory.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/SessionFactoryStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/SessionStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/connector/Connector.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorFactory.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorFactoryStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/connector/IConnector.ts
 create mode 100644 themes/squares/server/src/lib/authentication/backends/ldap/connector/IConnectorFactory.ts
 create mode 100644 themes/squares/server/src/lib/authentication/totp/ITotpHandler.ts
 create mode 100644 themes/squares/server/src/lib/authentication/totp/TotpHandler.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/totp/TotpHandler.ts
 create mode 100644 themes/squares/server/src/lib/authentication/totp/TotpHandlerStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/authentication/u2f/IU2fHandler.ts
 create mode 100644 themes/squares/server/src/lib/authentication/u2f/U2fHandler.ts
 create mode 100644 themes/squares/server/src/lib/authentication/u2f/U2fHandlerStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/authorization/Authorizer.spec.ts
 create mode 100644 themes/squares/server/src/lib/authorization/Authorizer.ts
 create mode 100644 themes/squares/server/src/lib/authorization/AuthorizerStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/authorization/IAuthorizer.ts
 create mode 100644 themes/squares/server/src/lib/authorization/Level.ts
 create mode 100644 themes/squares/server/src/lib/authorization/MultipleDomainMatcher.ts
 create mode 100644 themes/squares/server/src/lib/authorization/Object.ts
 create mode 100644 themes/squares/server/src/lib/authorization/Subject.ts
 create mode 100644 themes/squares/server/src/lib/configuration/ConfigurationParser.spec.ts
 create mode 100644 themes/squares/server/src/lib/configuration/ConfigurationParser.ts
 create mode 100644 themes/squares/server/src/lib/configuration/SessionConfigurationBuilder.spec.ts
 create mode 100644 themes/squares/server/src/lib/configuration/SessionConfigurationBuilder.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/AclConfiguration.spec.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/AclConfiguration.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.spec.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/Configuration.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/FileUsersDatabaseConfiguration.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/LdapConfiguration.spec.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/LdapConfiguration.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/NotifierConfiguration.spec.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/NotifierConfiguration.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/RegulationConfiguration.spec.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/RegulationConfiguration.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/SessionConfiguration.spec.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/SessionConfiguration.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/StorageConfiguration.spec.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/StorageConfiguration.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/TotpConfiguration.ts
 create mode 100644 themes/squares/server/src/lib/configuration/schema/UserDatabaseConfiguration.ts
 create mode 100644 themes/squares/server/src/lib/connectors/mongo/IMongoClient.d.ts
 create mode 100644 themes/squares/server/src/lib/connectors/mongo/MongoClient.spec.ts
 create mode 100644 themes/squares/server/src/lib/connectors/mongo/MongoClient.ts
 create mode 100644 themes/squares/server/src/lib/connectors/mongo/MongoClientStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/logging/GlobalLogger.ts
 create mode 100644 themes/squares/server/src/lib/logging/GlobalLoggerStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/logging/IGlobalLogger.ts
 create mode 100644 themes/squares/server/src/lib/logging/IRequestLogger.ts
 create mode 100644 themes/squares/server/src/lib/logging/RequestLogger.ts
 create mode 100644 themes/squares/server/src/lib/logging/RequestLoggerStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/AbstractEmailNotifier.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/EmailNotifier.spec.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/EmailNotifier.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/FileSystemNotifier.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/IMailSender.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/IMailSenderBuilder.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/INotifier.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/MailSender.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/MailSenderBuilder.spec.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/MailSenderBuilder.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/MailSenderBuilderStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/MailSenderStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/NotifierFactory.spec.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/NotifierFactory.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/NotifierStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/notifiers/SmtpNotifier.ts
 create mode 100644 themes/squares/server/src/lib/regulation/IRegulator.ts
 create mode 100644 themes/squares/server/src/lib/regulation/Regulator.spec.ts
 create mode 100644 themes/squares/server/src/lib/regulation/Regulator.ts
 create mode 100644 themes/squares/server/src/lib/regulation/RegulatorStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/error/401/get.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/error/401/get.ts
 create mode 100644 themes/squares/server/src/lib/routes/error/403/get.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/error/403/get.ts
 create mode 100644 themes/squares/server/src/lib/routes/error/404/get.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/error/404/get.ts
 create mode 100644 themes/squares/server/src/lib/routes/error/redirector.ts
 create mode 100644 themes/squares/server/src/lib/routes/firstfactor/get.ts
 create mode 100644 themes/squares/server/src/lib/routes/firstfactor/post.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/firstfactor/post.ts
 create mode 100644 themes/squares/server/src/lib/routes/loggedin/get.ts
 create mode 100644 themes/squares/server/src/lib/routes/logout/get.ts
 create mode 100644 themes/squares/server/src/lib/routes/password-reset/constants.ts
 create mode 100644 themes/squares/server/src/lib/routes/password-reset/form/post.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/password-reset/form/post.ts
 create mode 100644 themes/squares/server/src/lib/routes/password-reset/identity/PasswordResetHandler.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts
 create mode 100644 themes/squares/server/src/lib/routes/password-reset/request/get.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/get.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/get.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/redirect.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/redirect.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/totp/constants.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/totp/sign/post.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/totp/sign/post.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/u2f/U2FCommon.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/u2f/register/post.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/u2f/register/post.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/u2f/register_request/get.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/u2f/register_request/get.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/u2f/sign/post.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/u2f/sign/post.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts
 create mode 100644 themes/squares/server/src/lib/routes/verify/access_control.ts
 create mode 100644 themes/squares/server/src/lib/routes/verify/get.spec.ts
 create mode 100644 themes/squares/server/src/lib/routes/verify/get.ts
 create mode 100644 themes/squares/server/src/lib/routes/verify/get_basic_auth.ts
 create mode 100644 themes/squares/server/src/lib/routes/verify/get_session_cookie.ts
 create mode 100644 themes/squares/server/src/lib/storage/AuthenticationTraceDocument.d.ts
 create mode 100644 themes/squares/server/src/lib/storage/CollectionFactoryFactory.ts
 create mode 100644 themes/squares/server/src/lib/storage/CollectionFactoryStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/storage/CollectionStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/storage/ICollection.d.ts
 create mode 100644 themes/squares/server/src/lib/storage/ICollectionFactory.d.ts
 create mode 100644 themes/squares/server/src/lib/storage/IUserDataStore.d.ts
 create mode 100644 themes/squares/server/src/lib/storage/IdentityValidationDocument.d.ts
 create mode 100644 themes/squares/server/src/lib/storage/TOTPSecretDocument.d.ts
 create mode 100644 themes/squares/server/src/lib/storage/U2FRegistrationDocument.d.ts
 create mode 100644 themes/squares/server/src/lib/storage/UserDataStore.spec.ts
 create mode 100644 themes/squares/server/src/lib/storage/UserDataStore.ts
 create mode 100644 themes/squares/server/src/lib/storage/UserDataStoreStub.spec.ts
 create mode 100644 themes/squares/server/src/lib/storage/mongo/MongoCollection.spec.ts
 create mode 100644 themes/squares/server/src/lib/storage/mongo/MongoCollection.ts
 create mode 100644 themes/squares/server/src/lib/storage/mongo/MongoCollectionFactory.spec.ts
 create mode 100644 themes/squares/server/src/lib/storage/mongo/MongoCollectionFactory.ts
 create mode 100644 themes/squares/server/src/lib/storage/nedb/NedbCollection.spec.ts
 create mode 100644 themes/squares/server/src/lib/storage/nedb/NedbCollection.ts
 create mode 100644 themes/squares/server/src/lib/storage/nedb/NedbCollectionFactory.spec.ts
 create mode 100644 themes/squares/server/src/lib/storage/nedb/NedbCollectionFactory.ts
 create mode 100644 themes/squares/server/src/lib/stubs/express.spec.ts
 create mode 100644 themes/squares/server/src/lib/stubs/ldapjs.spec.ts
 create mode 100644 themes/squares/server/src/lib/stubs/speakeasy.spec.ts
 create mode 100644 themes/squares/server/src/lib/stubs/u2f.spec.ts
 create mode 100644 themes/squares/server/src/lib/utils/HashGenerator.spec.ts
 create mode 100644 themes/squares/server/src/lib/utils/HashGenerator.ts
 create mode 100644 themes/squares/server/src/lib/utils/ObjectCloner.ts
 create mode 100644 themes/squares/server/src/lib/utils/SafeRedirection.spec.ts
 create mode 100644 themes/squares/server/src/lib/utils/SafeRedirection.ts
 create mode 100644 themes/squares/server/src/lib/utils/URLDecomposer.spec.ts
 create mode 100644 themes/squares/server/src/lib/utils/URLDecomposer.ts
 create mode 100644 themes/squares/server/src/lib/web_server/Configurator.ts
 create mode 100644 themes/squares/server/src/lib/web_server/RestApi.ts
 create mode 100644 themes/squares/server/src/lib/web_server/middlewares/RequireValidatedFirstFactor.ts
 create mode 100644 themes/squares/server/src/lib/web_server/middlewares/WithHeadersLogged.ts
 create mode 100644 themes/squares/server/src/resources/email-template.ejs
 create mode 100644 themes/squares/server/src/views/already-logged-in.pug
 create mode 100644 themes/squares/server/src/views/errors/.directory
 create mode 100644 themes/squares/server/src/views/errors/401.pug
 create mode 100644 themes/squares/server/src/views/errors/403.pug
 create mode 100644 themes/squares/server/src/views/errors/404.pug
 create mode 100644 themes/squares/server/src/views/firstfactor.pug
 create mode 100644 themes/squares/server/src/views/layout/layout.pug
 create mode 100644 themes/squares/server/src/views/need-identity-validation.pug
 create mode 100644 themes/squares/server/src/views/password-reset-form.pug
 create mode 100644 themes/squares/server/src/views/password-reset-request.pug
 create mode 100644 themes/squares/server/src/views/secondfactor.pug
 create mode 100644 themes/squares/server/src/views/totp-register.pug
 create mode 100644 themes/squares/server/src/views/u2f-register.pug
 create mode 100644 themes/squares/server/test/requests.ts
 create mode 100644 themes/squares/server/tsconfig.json
 create mode 100644 themes/squares/server/tslint.json
 create mode 100644 themes/squares/server/types/.directory
 create mode 100644 themes/squares/server/types/AuthenticationSession.ts
 create mode 100644 themes/squares/server/types/Dependencies.ts
 create mode 100644 themes/squares/server/types/Identity.ts
 create mode 100644 themes/squares/server/types/TOTPSecret.ts
 create mode 100644 themes/squares/server/types/U2FRegistration.ts
 create mode 100644 themes/squares/server/types/dovehash.d.ts
 create mode 100644 themes/squares/server/types/speakeasy.d.ts
 create mode 100644 themes/triangles/client/src/.directory
 create mode 100644 themes/triangles/client/src/css/.directory
 create mode 100644 themes/triangles/client/src/css/00-bootstrap.min.css
 create mode 100644 themes/triangles/client/src/css/01-main.css
 create mode 100644 themes/triangles/client/src/css/02-login.css
 create mode 100644 themes/triangles/client/src/css/03-errors.css
 create mode 100644 themes/triangles/client/src/css/03-password-reset-form.css
 create mode 100644 themes/triangles/client/src/css/03-password-reset-request.css
 create mode 100644 themes/triangles/client/src/css/03-totp-register.css
 create mode 100644 themes/triangles/client/src/css/03-u2f-register.css
 create mode 100644 themes/triangles/client/src/img/LargeTriangles.svg
 create mode 100644 themes/triangles/client/src/img/background.jpg
 create mode 100644 themes/triangles/client/src/img/icon.png
 create mode 100644 themes/triangles/client/src/img/mail.png
 create mode 100644 themes/triangles/client/src/img/matrix_circle_128x128.png
 create mode 100644 themes/triangles/client/src/img/notifications/.directory
 create mode 100644 themes/triangles/client/src/img/notifications/error.png
 create mode 100644 themes/triangles/client/src/img/notifications/info.png
 create mode 100644 themes/triangles/client/src/img/notifications/success.png
 create mode 100644 themes/triangles/client/src/img/notifications/warning.png
 create mode 100644 themes/triangles/client/src/img/padlock.png
 create mode 100644 themes/triangles/client/src/img/password_white.png
 create mode 100644 themes/triangles/client/src/img/pendrive.png
 create mode 100644 themes/triangles/client/src/img/sharingan.png
 create mode 100644 themes/triangles/client/src/img/stores/.directory
 create mode 100644 themes/triangles/client/src/img/stores/applestore-badge.svg
 create mode 100644 themes/triangles/client/src/img/stores/googleplay-badge.svg
 create mode 100644 themes/triangles/client/src/img/success.png
 create mode 100644 themes/triangles/client/src/img/user.png
 create mode 100644 themes/triangles/client/src/img/warning.png
 create mode 100644 themes/triangles/client/src/index.ts
 create mode 100644 themes/triangles/client/src/lib/GetPromised.ts
 create mode 100644 themes/triangles/client/src/lib/INotifier.ts
 create mode 100644 themes/triangles/client/src/lib/Notifier.ts
 create mode 100644 themes/triangles/client/src/lib/QueryParametersRetriever.ts
 create mode 100644 themes/triangles/client/src/lib/SafeRedirect.ts
 create mode 100644 themes/triangles/client/src/lib/firstfactor/FirstFactorValidator.ts
 create mode 100644 themes/triangles/client/src/lib/firstfactor/UISelectors.ts
 create mode 100644 themes/triangles/client/src/lib/firstfactor/index.ts
 create mode 100644 themes/triangles/client/src/lib/reset-password/constants.ts
 create mode 100644 themes/triangles/client/src/lib/reset-password/reset-password-form.ts
 create mode 100644 themes/triangles/client/src/lib/reset-password/reset-password-request.ts
 create mode 100644 themes/triangles/client/src/lib/secondfactor/TOTPValidator.ts
 create mode 100644 themes/triangles/client/src/lib/secondfactor/U2FValidator.ts
 create mode 100644 themes/triangles/client/src/lib/secondfactor/constants.ts
 create mode 100644 themes/triangles/client/src/lib/secondfactor/index.ts
 create mode 100644 themes/triangles/client/src/lib/totp-register/totp-register.ts
 create mode 100644 themes/triangles/client/src/lib/totp-register/ui-selector.ts
 create mode 100644 themes/triangles/client/src/lib/u2f-register/u2f-register.ts
 create mode 100644 themes/triangles/client/src/thirdparties/qrcode.min.js
 create mode 100644 themes/triangles/client/src/thirdparties/u2f-api.js
 create mode 100644 themes/triangles/client/test/Notifier.test.ts
 create mode 100644 themes/triangles/client/test/firstfactor/FirstFactorValidator.test.ts
 create mode 100644 themes/triangles/client/test/mocks/NotifierStub.ts
 create mode 100644 themes/triangles/client/test/mocks/jquery.ts
 create mode 100644 themes/triangles/client/test/mocks/u2f-api.ts
 create mode 100644 themes/triangles/client/test/secondfactor/TOTPValidator.test.ts
 create mode 100644 themes/triangles/client/test/totp-register/totp-register.test.ts
 create mode 100644 themes/triangles/client/tsconfig.json
 create mode 100644 themes/triangles/client/tslint.json
 create mode 100644 themes/triangles/server/.directory
 create mode 100755 themes/triangles/server/src/index.ts
 create mode 100644 themes/triangles/server/src/lib/.directory
 create mode 100644 themes/triangles/server/src/lib/AuthenticationSessionHandler.ts
 create mode 100644 themes/triangles/server/src/lib/ErrorReplies.ts
 create mode 100644 themes/triangles/server/src/lib/Exceptions.ts
 create mode 100644 themes/triangles/server/src/lib/FirstFactorValidator.ts
 create mode 100644 themes/triangles/server/src/lib/IdentityCheckMiddleware.spec.ts
 create mode 100644 themes/triangles/server/src/lib/IdentityCheckMiddleware.ts
 create mode 100644 themes/triangles/server/src/lib/IdentityCheckPreValidationTemplate.ts
 create mode 100644 themes/triangles/server/src/lib/IdentityValidable.ts
 create mode 100644 themes/triangles/server/src/lib/IdentityValidableStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/Server.spec.ts
 create mode 100644 themes/triangles/server/src/lib/Server.ts
 create mode 100644 themes/triangles/server/src/lib/ServerVariables.ts
 create mode 100644 themes/triangles/server/src/lib/ServerVariablesInitializer.ts
 create mode 100644 themes/triangles/server/src/lib/ServerVariablesMockBuilder.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/Level.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/GroupsAndEmails.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/IUsersDatabase.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/IUsersDatabaseStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/file/FileUsersDatabase.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/file/FileUsersDatabase.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/file/ReadWriteQueue.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/ISession.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/ISessionFactory.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/SafeSession.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/SafeSession.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/Sanitizer.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/Sanitizer.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/Session.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/Session.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/SessionFactory.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/SessionFactoryStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/SessionStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/connector/Connector.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorFactory.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorFactoryStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/connector/IConnector.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/backends/ldap/connector/IConnectorFactory.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/totp/ITotpHandler.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/totp/TotpHandler.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/totp/TotpHandler.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/totp/TotpHandlerStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/u2f/IU2fHandler.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/u2f/U2fHandler.ts
 create mode 100644 themes/triangles/server/src/lib/authentication/u2f/U2fHandlerStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authorization/Authorizer.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authorization/Authorizer.ts
 create mode 100644 themes/triangles/server/src/lib/authorization/AuthorizerStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/authorization/IAuthorizer.ts
 create mode 100644 themes/triangles/server/src/lib/authorization/Level.ts
 create mode 100644 themes/triangles/server/src/lib/authorization/MultipleDomainMatcher.ts
 create mode 100644 themes/triangles/server/src/lib/authorization/Object.ts
 create mode 100644 themes/triangles/server/src/lib/authorization/Subject.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/ConfigurationParser.spec.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/ConfigurationParser.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/SessionConfigurationBuilder.spec.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/SessionConfigurationBuilder.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/AclConfiguration.spec.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/AclConfiguration.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.spec.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/Configuration.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/FileUsersDatabaseConfiguration.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/LdapConfiguration.spec.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/LdapConfiguration.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/NotifierConfiguration.spec.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/NotifierConfiguration.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/RegulationConfiguration.spec.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/RegulationConfiguration.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/SessionConfiguration.spec.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/SessionConfiguration.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/StorageConfiguration.spec.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/StorageConfiguration.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/TotpConfiguration.ts
 create mode 100644 themes/triangles/server/src/lib/configuration/schema/UserDatabaseConfiguration.ts
 create mode 100644 themes/triangles/server/src/lib/connectors/mongo/IMongoClient.d.ts
 create mode 100644 themes/triangles/server/src/lib/connectors/mongo/MongoClient.spec.ts
 create mode 100644 themes/triangles/server/src/lib/connectors/mongo/MongoClient.ts
 create mode 100644 themes/triangles/server/src/lib/connectors/mongo/MongoClientStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/logging/GlobalLogger.ts
 create mode 100644 themes/triangles/server/src/lib/logging/GlobalLoggerStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/logging/IGlobalLogger.ts
 create mode 100644 themes/triangles/server/src/lib/logging/IRequestLogger.ts
 create mode 100644 themes/triangles/server/src/lib/logging/RequestLogger.ts
 create mode 100644 themes/triangles/server/src/lib/logging/RequestLoggerStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/AbstractEmailNotifier.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/EmailNotifier.spec.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/EmailNotifier.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/FileSystemNotifier.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/IMailSender.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/IMailSenderBuilder.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/INotifier.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/MailSender.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/MailSenderBuilder.spec.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/MailSenderBuilder.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/MailSenderBuilderStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/MailSenderStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/NotifierFactory.spec.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/NotifierFactory.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/NotifierStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/notifiers/SmtpNotifier.ts
 create mode 100644 themes/triangles/server/src/lib/regulation/IRegulator.ts
 create mode 100644 themes/triangles/server/src/lib/regulation/Regulator.spec.ts
 create mode 100644 themes/triangles/server/src/lib/regulation/Regulator.ts
 create mode 100644 themes/triangles/server/src/lib/regulation/RegulatorStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/error/401/get.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/error/401/get.ts
 create mode 100644 themes/triangles/server/src/lib/routes/error/403/get.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/error/403/get.ts
 create mode 100644 themes/triangles/server/src/lib/routes/error/404/get.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/error/404/get.ts
 create mode 100644 themes/triangles/server/src/lib/routes/error/redirector.ts
 create mode 100644 themes/triangles/server/src/lib/routes/firstfactor/get.ts
 create mode 100644 themes/triangles/server/src/lib/routes/firstfactor/post.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/firstfactor/post.ts
 create mode 100644 themes/triangles/server/src/lib/routes/loggedin/get.ts
 create mode 100644 themes/triangles/server/src/lib/routes/logout/get.ts
 create mode 100644 themes/triangles/server/src/lib/routes/password-reset/constants.ts
 create mode 100644 themes/triangles/server/src/lib/routes/password-reset/form/post.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/password-reset/form/post.ts
 create mode 100644 themes/triangles/server/src/lib/routes/password-reset/identity/PasswordResetHandler.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts
 create mode 100644 themes/triangles/server/src/lib/routes/password-reset/request/get.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/get.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/get.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/redirect.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/redirect.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/totp/constants.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/totp/sign/post.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/totp/sign/post.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/u2f/U2FCommon.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/u2f/register/post.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/u2f/register/post.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/u2f/register_request/get.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/u2f/register_request/get.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/u2f/sign/post.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/u2f/sign/post.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts
 create mode 100644 themes/triangles/server/src/lib/routes/verify/access_control.ts
 create mode 100644 themes/triangles/server/src/lib/routes/verify/get.spec.ts
 create mode 100644 themes/triangles/server/src/lib/routes/verify/get.ts
 create mode 100644 themes/triangles/server/src/lib/routes/verify/get_basic_auth.ts
 create mode 100644 themes/triangles/server/src/lib/routes/verify/get_session_cookie.ts
 create mode 100644 themes/triangles/server/src/lib/storage/AuthenticationTraceDocument.d.ts
 create mode 100644 themes/triangles/server/src/lib/storage/CollectionFactoryFactory.ts
 create mode 100644 themes/triangles/server/src/lib/storage/CollectionFactoryStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/storage/CollectionStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/storage/ICollection.d.ts
 create mode 100644 themes/triangles/server/src/lib/storage/ICollectionFactory.d.ts
 create mode 100644 themes/triangles/server/src/lib/storage/IUserDataStore.d.ts
 create mode 100644 themes/triangles/server/src/lib/storage/IdentityValidationDocument.d.ts
 create mode 100644 themes/triangles/server/src/lib/storage/TOTPSecretDocument.d.ts
 create mode 100644 themes/triangles/server/src/lib/storage/U2FRegistrationDocument.d.ts
 create mode 100644 themes/triangles/server/src/lib/storage/UserDataStore.spec.ts
 create mode 100644 themes/triangles/server/src/lib/storage/UserDataStore.ts
 create mode 100644 themes/triangles/server/src/lib/storage/UserDataStoreStub.spec.ts
 create mode 100644 themes/triangles/server/src/lib/storage/mongo/MongoCollection.spec.ts
 create mode 100644 themes/triangles/server/src/lib/storage/mongo/MongoCollection.ts
 create mode 100644 themes/triangles/server/src/lib/storage/mongo/MongoCollectionFactory.spec.ts
 create mode 100644 themes/triangles/server/src/lib/storage/mongo/MongoCollectionFactory.ts
 create mode 100644 themes/triangles/server/src/lib/storage/nedb/NedbCollection.spec.ts
 create mode 100644 themes/triangles/server/src/lib/storage/nedb/NedbCollection.ts
 create mode 100644 themes/triangles/server/src/lib/storage/nedb/NedbCollectionFactory.spec.ts
 create mode 100644 themes/triangles/server/src/lib/storage/nedb/NedbCollectionFactory.ts
 create mode 100644 themes/triangles/server/src/lib/stubs/express.spec.ts
 create mode 100644 themes/triangles/server/src/lib/stubs/ldapjs.spec.ts
 create mode 100644 themes/triangles/server/src/lib/stubs/speakeasy.spec.ts
 create mode 100644 themes/triangles/server/src/lib/stubs/u2f.spec.ts
 create mode 100644 themes/triangles/server/src/lib/utils/HashGenerator.spec.ts
 create mode 100644 themes/triangles/server/src/lib/utils/HashGenerator.ts
 create mode 100644 themes/triangles/server/src/lib/utils/ObjectCloner.ts
 create mode 100644 themes/triangles/server/src/lib/utils/SafeRedirection.spec.ts
 create mode 100644 themes/triangles/server/src/lib/utils/SafeRedirection.ts
 create mode 100644 themes/triangles/server/src/lib/utils/URLDecomposer.spec.ts
 create mode 100644 themes/triangles/server/src/lib/utils/URLDecomposer.ts
 create mode 100644 themes/triangles/server/src/lib/web_server/Configurator.ts
 create mode 100644 themes/triangles/server/src/lib/web_server/RestApi.ts
 create mode 100644 themes/triangles/server/src/lib/web_server/middlewares/RequireValidatedFirstFactor.ts
 create mode 100644 themes/triangles/server/src/lib/web_server/middlewares/WithHeadersLogged.ts
 create mode 100644 themes/triangles/server/src/resources/email-template.ejs
 create mode 100644 themes/triangles/server/src/views/already-logged-in.pug
 create mode 100644 themes/triangles/server/src/views/errors/.directory
 create mode 100644 themes/triangles/server/src/views/errors/401.pug
 create mode 100644 themes/triangles/server/src/views/errors/403.pug
 create mode 100644 themes/triangles/server/src/views/errors/404.pug
 create mode 100644 themes/triangles/server/src/views/firstfactor.pug
 create mode 100644 themes/triangles/server/src/views/layout/layout.pug
 create mode 100644 themes/triangles/server/src/views/need-identity-validation.pug
 create mode 100644 themes/triangles/server/src/views/password-reset-form.pug
 create mode 100644 themes/triangles/server/src/views/password-reset-request.pug
 create mode 100644 themes/triangles/server/src/views/secondfactor.pug
 create mode 100644 themes/triangles/server/src/views/totp-register.pug
 create mode 100644 themes/triangles/server/src/views/u2f-register.pug
 create mode 100644 themes/triangles/server/test/requests.ts
 create mode 100644 themes/triangles/server/tsconfig.json
 create mode 100644 themes/triangles/server/tslint.json
 create mode 100644 themes/triangles/server/types/.directory
 create mode 100644 themes/triangles/server/types/AuthenticationSession.ts
 create mode 100644 themes/triangles/server/types/Dependencies.ts
 create mode 100644 themes/triangles/server/types/Identity.ts
 create mode 100644 themes/triangles/server/types/TOTPSecret.ts
 create mode 100644 themes/triangles/server/types/U2FRegistration.ts
 create mode 100644 themes/triangles/server/types/dovehash.d.ts
 create mode 100644 themes/triangles/server/types/speakeasy.d.ts

diff --git a/Gruntfile.js b/Gruntfile.js
index 1509cc90..fcf4a573 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -155,6 +155,54 @@ module.exports = function (grunt) {
             src: '**',
             dest: `${buildDir}/server/src/public_html/js/`
         },
+        squares_resources: {
+            expand: true,
+            cwd: 'themes/squares/server/src/resources',
+            src: '**',
+            dest: `${buildDir}/server/src/resources/`
+        },
+        squares_views: {
+            expand: true,
+            cwd: 'themes/squares/server/src/views',
+            src: '**',
+            dest: `${buildDir}/server/src/views/`
+        },
+        squares_images: {
+            expand: true,
+            cwd: 'themes/squares/client/src/img',
+            src: '**',
+            dest: `${buildDir}/server/src/public_html/img/`
+        },
+        squares_thirdparties: {
+            expand: true,
+            cwd: 'themes/squares/client/src/thirdparties',
+            src: '**',
+            dest: `${buildDir}/server/src/public_html/js/`
+        },
+        triangles_resources: {
+            expand: true,
+            cwd: 'themes/triangles/server/src/resources',
+            src: '**',
+            dest: `${buildDir}/server/src/resources/`
+        },
+        triangles_views: {
+            expand: true,
+            cwd: 'themes/triangles/server/src/views',
+            src: '**',
+            dest: `${buildDir}/server/src/views/`
+        },
+        triangles_images: {
+            expand: true,
+            cwd: 'themes/triangles/client/src/img',
+            src: '**',
+            dest: `${buildDir}/server/src/public_html/img/`
+        },
+        triangles_thirdparties: {
+            expand: true,
+            cwd: 'themes/triangles/client/src/thirdparties',
+            src: '**',
+            dest: `${buildDir}/server/src/public_html/js/`
+        },
         schema: {
             src: schemaDir,
             dest: `${buildDir}/${schemaDir}`
@@ -234,6 +282,14 @@ module.exports = function (grunt) {
         src: ['themes/black/client/src/css/*.css'],
         dest: `${buildDir}/server/src/public_html/css/authelia.css`
       },
+      squares_css: {
+        src: ['themes/squares/client/src/css/*.css'],
+        dest: `${buildDir}/server/src/public_html/css/authelia.css`
+      },
+      triangles_css: {
+        src: ['themes/triangles/client/src/css/*.css'],
+        dest: `${buildDir}/server/src/public_html/css/authelia.css`
+      },
     },
     cssmin: {
       target: {
@@ -266,18 +322,24 @@ module.exports = function (grunt) {
   grunt.registerTask('test-int', ['run:test-cucumber', 'run:test-minimal-config', 'run:test-complete-config', 'run:test-inactivity']);
 
   grunt.registerTask('copy-resources-main', ['copy:main_resources', 'copy:main_views', 'copy:main_images', 'copy:main_thirdparties', 'concat:main_css']);
-  
-  grunt.registerTask('generate-config-schema', ['run:generate-config-schema', 'copy:schema']);
 
   grunt.registerTask('copy-resources-matrix', ['copy:matrix_resources', 'copy:matrix_views', 'copy:matrix_images', 'copy:matrix_thirdparties', 'concat:matrix_css']);
   
   grunt.registerTask('copy-resources-black', ['copy:black_resources', 'copy:black_views', 'copy:black_images', 'copy:black_thirdparties', 'concat:black_css']);
+
+  grunt.registerTask('copy-resources-squares', ['copy:squares_resources', 'copy:squares_views', 'copy:squares_images', 'copy:squares_thirdparties', 'concat:squares_css']);
+  
+  grunt.registerTask('copy-resources-triangles', ['copy:triangles_resources', 'copy:triangles_views', 'copy:triangles_images', 'copy:triangles_thirdparties', 'concat:triangles_css']);
+  
+  grunt.registerTask('generate-config-schema', ['run:generate-config-schema', 'copy:schema']);
   
   grunt.registerTask('build-client', ['compile-client', 'browserify']);
   grunt.registerTask('build-server-main', ['compile-server', 'copy-resources-main', 'generate-config-schema']);
   grunt.registerTask('build-server-matrix', ['compile-server', 'copy-resources-matrix', 'generate-config-schema']);
   grunt.registerTask('build-server-black', ['compile-server', 'copy-resources-black', 'generate-config-schema']);
-    
+  grunt.registerTask('build-server-squares', ['compile-server', 'copy-resources-squares', 'generate-config-schema']);
+  grunt.registerTask('build-server-triangles', ['compile-server', 'copy-resources-triangles', 'generate-config-schema']);
+  
   grunt.registerTask('build', ['build-client', 'build-server-'+target]);
   grunt.registerTask('build-dist', ['clean', 'build', 'run:minify', 'cssmin', 'run:include-minified-script']);
   
diff --git a/themes/squares/client/src/css/.directory b/themes/squares/client/src/css/.directory
new file mode 100644
index 00000000..6e4b3f63
--- /dev/null
+++ b/themes/squares/client/src/css/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,56,41
+Version=3
+ViewMode=1
diff --git a/themes/squares/client/src/css/00-bootstrap.min.css b/themes/squares/client/src/css/00-bootstrap.min.css
new file mode 100644
index 00000000..dfeacbb8
--- /dev/null
+++ b/themes/squares/client/src/css/00-bootstrap.min.css
@@ -0,0 +1,5768 @@
+/*! * Bootstrap v3.3.7 (http://getbootstrap.com) * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+html{
+    font-family:sans-serif;
+    -webkit-text-size-adjust:100%;
+    -ms-text-size-adjust:100%
+}
+body{
+    margin:0
+}
+article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{
+    display:block
+}
+audio,canvas,progress,video{
+    display:inline-block;
+    vertical-align:baseline
+}
+audio:not([controls]){
+    display:none;
+    height:0
+}
+[hidden],template{
+    display:none
+}
+a{
+    background-color:transparent
+}
+a:active,a:hover{
+    outline:0
+}
+abbr[title]{
+    border-bottom:1px dotted
+}
+b,strong{
+    font-weight:700
+}
+dfn{
+    font-style:italic
+}
+h1{
+    margin:.67em 0;
+    font-size:2em
+}
+mark{
+    color:#000;
+    background:#ff0
+}
+small{
+    font-size:80%
+}
+sub,sup{
+    position:relative;
+    font-size:75%;
+    line-height:0;
+    vertical-align:baseline
+}
+sup{
+    top:-.5em
+}
+sub{
+    bottom:-.25em
+}
+img{
+    border:0
+}
+svg:not(:root){
+    overflow:hidden
+}
+figure{
+    margin:1em 40px
+}
+hr{
+    height:0;
+    -webkit-box-sizing:content-box;
+    -moz-box-sizing:content-box;
+    box-sizing:content-box
+}
+pre{
+    overflow:auto
+}
+code,kbd,pre,samp{
+    font-family:monospace,monospace;
+    font-size:1em
+}
+button,input,optgroup,select,textarea{
+    margin:0;
+    font:inherit;
+    color:inherit
+}
+button{
+    overflow:visible
+}
+button,select{
+    text-transform:none
+}
+button,html input[type=button],input[type=reset],input[type=submit]{
+    -webkit-appearance:button;
+    cursor:pointer
+}
+button[disabled],html input[disabled]{
+    cursor:default
+}
+button::-moz-focus-inner,input::-moz-focus-inner{
+    padding:0;
+    border:0
+}
+input{
+    line-height:normal
+}
+input[type=checkbox],input[type=radio]{
+    -webkit-box-sizing:border-box;
+    -moz-box-sizing:border-box;
+    box-sizing:border-box;
+    padding:0
+}
+input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{
+    height:auto
+}
+input[type=search]{
+    -webkit-box-sizing:content-box;
+    -moz-box-sizing:content-box;
+    box-sizing:content-box;
+    -webkit-appearance:textfield
+}
+input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{
+    -webkit-appearance:none
+}
+fieldset{
+    padding:.35em .625em .75em;
+    margin:0 2px;
+    border:1px solid silver
+}
+legend{
+    padding:0;
+    border:0
+}
+textarea{
+    overflow:auto
+}
+optgroup{
+    font-weight:700
+}
+table{
+    border-spacing:0;
+    border-collapse:collapse
+}
+td,th{
+    padding:0
+}
+/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */
+@media print{
+    *,:after,:before{
+        color:#000!important;
+        text-shadow:none!important;
+        background:0 0!important;
+        -webkit-box-shadow:none!important;
+        box-shadow:none!important
+    }
+    a,a:visited{
+        text-decoration:underline
+    }
+    a[href]:after{
+        content:" (" attr(href) ")"
+    }
+    abbr[title]:after{
+        content:" (" attr(title) ")"
+    }
+    a[href^="javascript:"]:after,a[href^="#"]:after{
+        content:""
+    }
+    blockquote,pre{
+        border:1px solid #999;
+        page-break-inside:avoid
+    }
+    thead{
+        display:table-header-group
+    }
+    img,tr{
+        page-break-inside:avoid
+    }
+    img{
+        max-width:100%!important
+    }
+    h2,h3,p{
+        orphans:3;
+        widows:3
+    }
+    h2,h3{
+        page-break-after:avoid
+    }
+    .navbar{
+        display:none
+    }
+    .btn>.caret,.dropup>.btn>.caret{
+        border-top-color:#000!important
+    }
+    .label{
+        border:1px solid #000
+    }
+    .table{
+        border-collapse:collapse!important
+    }
+    .table td,.table th{
+        background-color:#fff!important
+    }
+    .table-bordered td,.table-bordered th{
+        border:1px solid #ddd!important
+    }
+}
+@font-face{
+    font-family:'Glyphicons Halflings';
+    src:url(../fonts/glyphicons-halflings-regular.eot);
+    src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')
+}
+.glyphicon{
+    position:relative;
+    top:1px;
+    display:inline-block;
+    font-family:'Glyphicons Halflings';
+    font-style:normal;
+    font-weight:400;
+    line-height:1;
+    -webkit-font-smoothing:antialiased;
+    -moz-osx-font-smoothing:grayscale
+}
+.glyphicon-asterisk:before{
+    content:"\002a"
+}
+.glyphicon-plus:before{
+    content:"\002b"
+}
+.glyphicon-eur:before,.glyphicon-euro:before{
+    content:"\20ac"
+}
+.glyphicon-minus:before{
+    content:"\2212"
+}
+.glyphicon-cloud:before{
+    content:"\2601"
+}
+.glyphicon-envelope:before{
+    content:"\2709"
+}
+.glyphicon-pencil:before{
+    content:"\270f"
+}
+.glyphicon-glass:before{
+    content:"\e001"
+}
+.glyphicon-music:before{
+    content:"\e002"
+}
+.glyphicon-search:before{
+    content:"\e003"
+}
+.glyphicon-heart:before{
+    content:"\e005"
+}
+.glyphicon-star:before{
+    content:"\e006"
+}
+.glyphicon-star-empty:before{
+    content:"\e007"
+}
+.glyphicon-user:before{
+    content:"\e008"
+}
+.glyphicon-film:before{
+    content:"\e009"
+}
+.glyphicon-th-large:before{
+    content:"\e010"
+}
+.glyphicon-th:before{
+    content:"\e011"
+}
+.glyphicon-th-list:before{
+    content:"\e012"
+}
+.glyphicon-ok:before{
+    content:"\e013"
+}
+.glyphicon-remove:before{
+    content:"\e014"
+}
+.glyphicon-zoom-in:before{
+    content:"\e015"
+}
+.glyphicon-zoom-out:before{
+    content:"\e016"
+}
+.glyphicon-off:before{
+    content:"\e017"
+}
+.glyphicon-signal:before{
+    content:"\e018"
+}
+.glyphicon-cog:before{
+    content:"\e019"
+}
+.glyphicon-trash:before{
+    content:"\e020"
+}
+.glyphicon-home:before{
+    content:"\e021"
+}
+.glyphicon-file:before{
+    content:"\e022"
+}
+.glyphicon-time:before{
+    content:"\e023"
+}
+.glyphicon-road:before{
+    content:"\e024"
+}
+.glyphicon-download-alt:before{
+    content:"\e025"
+}
+.glyphicon-download:before{
+    content:"\e026"
+}
+.glyphicon-upload:before{
+    content:"\e027"
+}
+.glyphicon-inbox:before{
+    content:"\e028"
+}
+.glyphicon-play-circle:before{
+    content:"\e029"
+}
+.glyphicon-repeat:before{
+    content:"\e030"
+}
+.glyphicon-refresh:before{
+    content:"\e031"
+}
+.glyphicon-list-alt:before{
+    content:"\e032"
+}
+.glyphicon-lock:before{
+    content:"\e033"
+}
+.glyphicon-flag:before{
+    content:"\e034"
+}
+.glyphicon-headphones:before{
+    content:"\e035"
+}
+.glyphicon-volume-off:before{
+    content:"\e036"
+}
+.glyphicon-volume-down:before{
+    content:"\e037"
+}
+.glyphicon-volume-up:before{
+    content:"\e038"
+}
+.glyphicon-qrcode:before{
+    content:"\e039"
+}
+.glyphicon-barcode:before{
+    content:"\e040"
+}
+.glyphicon-tag:before{
+    content:"\e041"
+}
+.glyphicon-tags:before{
+    content:"\e042"
+}
+.glyphicon-book:before{
+    content:"\e043"
+}
+.glyphicon-bookmark:before{
+    content:"\e044"
+}
+.glyphicon-print:before{
+    content:"\e045"
+}
+.glyphicon-camera:before{
+    content:"\e046"
+}
+.glyphicon-font:before{
+    content:"\e047"
+}
+.glyphicon-bold:before{
+    content:"\e048"
+}
+.glyphicon-italic:before{
+    content:"\e049"
+}
+.glyphicon-text-height:before{
+    content:"\e050"
+}
+.glyphicon-text-width:before{
+    content:"\e051"
+}
+.glyphicon-align-left:before{
+    content:"\e052"
+}
+.glyphicon-align-center:before{
+    content:"\e053"
+}
+.glyphicon-align-right:before{
+    content:"\e054"
+}
+.glyphicon-align-justify:before{
+    content:"\e055"
+}
+.glyphicon-list:before{
+    content:"\e056"
+}
+.glyphicon-indent-left:before{
+    content:"\e057"
+}
+.glyphicon-indent-right:before{
+    content:"\e058"
+}
+.glyphicon-facetime-video:before{
+    content:"\e059"
+}
+.glyphicon-picture:before{
+    content:"\e060"
+}
+.glyphicon-map-marker:before{
+    content:"\e062"
+}
+.glyphicon-adjust:before{
+    content:"\e063"
+}
+.glyphicon-tint:before{
+    content:"\e064"
+}
+.glyphicon-edit:before{
+    content:"\e065"
+}
+.glyphicon-share:before{
+    content:"\e066"
+}
+.glyphicon-check:before{
+    content:"\e067"
+}
+.glyphicon-move:before{
+    content:"\e068"
+}
+.glyphicon-step-backward:before{
+    content:"\e069"
+}
+.glyphicon-fast-backward:before{
+    content:"\e070"
+}
+.glyphicon-backward:before{
+    content:"\e071"
+}
+.glyphicon-play:before{
+    content:"\e072"
+}
+.glyphicon-pause:before{
+    content:"\e073"
+}
+.glyphicon-stop:before{
+    content:"\e074"
+}
+.glyphicon-forward:before{
+    content:"\e075"
+}
+.glyphicon-fast-forward:before{
+    content:"\e076"
+}
+.glyphicon-step-forward:before{
+    content:"\e077"
+}
+.glyphicon-eject:before{
+    content:"\e078"
+}
+.glyphicon-chevron-left:before{
+    content:"\e079"
+}
+.glyphicon-chevron-right:before{
+    content:"\e080"
+}
+.glyphicon-plus-sign:before{
+    content:"\e081"
+}
+.glyphicon-minus-sign:before{
+    content:"\e082"
+}
+.glyphicon-remove-sign:before{
+    content:"\e083"
+}
+.glyphicon-ok-sign:before{
+    content:"\e084"
+}
+.glyphicon-question-sign:before{
+    content:"\e085"
+}
+.glyphicon-info-sign:before{
+    content:"\e086"
+}
+.glyphicon-screenshot:before{
+    content:"\e087"
+}
+.glyphicon-remove-circle:before{
+    content:"\e088"
+}
+.glyphicon-ok-circle:before{
+    content:"\e089"
+}
+.glyphicon-ban-circle:before{
+    content:"\e090"
+}
+.glyphicon-arrow-left:before{
+    content:"\e091"
+}
+.glyphicon-arrow-right:before{
+    content:"\e092"
+}
+.glyphicon-arrow-up:before{
+    content:"\e093"
+}
+.glyphicon-arrow-down:before{
+    content:"\e094"
+}
+.glyphicon-share-alt:before{
+    content:"\e095"
+}
+.glyphicon-resize-full:before{
+    content:"\e096"
+}
+.glyphicon-resize-small:before{
+    content:"\e097"
+}
+.glyphicon-exclamation-sign:before{
+    content:"\e101"
+}
+.glyphicon-gift:before{
+    content:"\e102"
+}
+.glyphicon-leaf:before{
+    content:"\e103"
+}
+.glyphicon-fire:before{
+    content:"\e104"
+}
+.glyphicon-eye-open:before{
+    content:"\e105"
+}
+.glyphicon-eye-close:before{
+    content:"\e106"
+}
+.glyphicon-warning-sign:before{
+    content:"\e107"
+}
+.glyphicon-plane:before{
+    content:"\e108"
+}
+.glyphicon-calendar:before{
+    content:"\e109"
+}
+.glyphicon-random:before{
+    content:"\e110"
+}
+.glyphicon-comment:before{
+    content:"\e111"
+}
+.glyphicon-magnet:before{
+    content:"\e112"
+}
+.glyphicon-chevron-up:before{
+    content:"\e113"
+}
+.glyphicon-chevron-down:before{
+    content:"\e114"
+}
+.glyphicon-retweet:before{
+    content:"\e115"
+}
+.glyphicon-shopping-cart:before{
+    content:"\e116"
+}
+.glyphicon-folder-close:before{
+    content:"\e117"
+}
+.glyphicon-folder-open:before{
+    content:"\e118"
+}
+.glyphicon-resize-vertical:before{
+    content:"\e119"
+}
+.glyphicon-resize-horizontal:before{
+    content:"\e120"
+}
+.glyphicon-hdd:before{
+    content:"\e121"
+}
+.glyphicon-bullhorn:before{
+    content:"\e122"
+}
+.glyphicon-bell:before{
+    content:"\e123"
+}
+.glyphicon-certificate:before{
+    content:"\e124"
+}
+.glyphicon-thumbs-up:before{
+    content:"\e125"
+}
+.glyphicon-thumbs-down:before{
+    content:"\e126"
+}
+.glyphicon-hand-right:before{
+    content:"\e127"
+}
+.glyphicon-hand-left:before{
+    content:"\e128"
+}
+.glyphicon-hand-up:before{
+    content:"\e129"
+}
+.glyphicon-hand-down:before{
+    content:"\e130"
+}
+.glyphicon-circle-arrow-right:before{
+    content:"\e131"
+}
+.glyphicon-circle-arrow-left:before{
+    content:"\e132"
+}
+.glyphicon-circle-arrow-up:before{
+    content:"\e133"
+}
+.glyphicon-circle-arrow-down:before{
+    content:"\e134"
+}
+.glyphicon-globe:before{
+    content:"\e135"
+}
+.glyphicon-wrench:before{
+    content:"\e136"
+}
+.glyphicon-tasks:before{
+    content:"\e137"
+}
+.glyphicon-filter:before{
+    content:"\e138"
+}
+.glyphicon-briefcase:before{
+    content:"\e139"
+}
+.glyphicon-fullscreen:before{
+    content:"\e140"
+}
+.glyphicon-dashboard:before{
+    content:"\e141"
+}
+.glyphicon-paperclip:before{
+    content:"\e142"
+}
+.glyphicon-heart-empty:before{
+    content:"\e143"
+}
+.glyphicon-link:before{
+    content:"\e144"
+}
+.glyphicon-phone:before{
+    content:"\e145"
+}
+.glyphicon-pushpin:before{
+    content:"\e146"
+}
+.glyphicon-usd:before{
+    content:"\e148"
+}
+.glyphicon-gbp:before{
+    content:"\e149"
+}
+.glyphicon-sort:before{
+    content:"\e150"
+}
+.glyphicon-sort-by-alphabet:before{
+    content:"\e151"
+}
+.glyphicon-sort-by-alphabet-alt:before{
+    content:"\e152"
+}
+.glyphicon-sort-by-order:before{
+    content:"\e153"
+}
+.glyphicon-sort-by-order-alt:before{
+    content:"\e154"
+}
+.glyphicon-sort-by-attributes:before{
+    content:"\e155"
+}
+.glyphicon-sort-by-attributes-alt:before{
+    content:"\e156"
+}
+.glyphicon-unchecked:before{
+    content:"\e157"
+}
+.glyphicon-expand:before{
+    content:"\e158"
+}
+.glyphicon-collapse-down:before{
+    content:"\e159"
+}
+.glyphicon-collapse-up:before{
+    content:"\e160"
+}
+.glyphicon-log-in:before{
+    content:"\e161"
+}
+.glyphicon-flash:before{
+    content:"\e162"
+}
+.glyphicon-log-out:before{
+    content:"\e163"
+}
+.glyphicon-new-window:before{
+    content:"\e164"
+}
+.glyphicon-record:before{
+    content:"\e165"
+}
+.glyphicon-save:before{
+    content:"\e166"
+}
+.glyphicon-open:before{
+    content:"\e167"
+}
+.glyphicon-saved:before{
+    content:"\e168"
+}
+.glyphicon-import:before{
+    content:"\e169"
+}
+.glyphicon-export:before{
+    content:"\e170"
+}
+.glyphicon-send:before{
+    content:"\e171"
+}
+.glyphicon-floppy-disk:before{
+    content:"\e172"
+}
+.glyphicon-floppy-saved:before{
+    content:"\e173"
+}
+.glyphicon-floppy-remove:before{
+    content:"\e174"
+}
+.glyphicon-floppy-save:before{
+    content:"\e175"
+}
+.glyphicon-floppy-open:before{
+    content:"\e176"
+}
+.glyphicon-credit-card:before{
+    content:"\e177"
+}
+.glyphicon-transfer:before{
+    content:"\e178"
+}
+.glyphicon-cutlery:before{
+    content:"\e179"
+}
+.glyphicon-header:before{
+    content:"\e180"
+}
+.glyphicon-compressed:before{
+    content:"\e181"
+}
+.glyphicon-earphone:before{
+    content:"\e182"
+}
+.glyphicon-phone-alt:before{
+    content:"\e183"
+}
+.glyphicon-tower:before{
+    content:"\e184"
+}
+.glyphicon-stats:before{
+    content:"\e185"
+}
+.glyphicon-sd-video:before{
+    content:"\e186"
+}
+.glyphicon-hd-video:before{
+    content:"\e187"
+}
+.glyphicon-subtitles:before{
+    content:"\e188"
+}
+.glyphicon-sound-stereo:before{
+    content:"\e189"
+}
+.glyphicon-sound-dolby:before{
+    content:"\e190"
+}
+.glyphicon-sound-5-1:before{
+    content:"\e191"
+}
+.glyphicon-sound-6-1:before{
+    content:"\e192"
+}
+.glyphicon-sound-7-1:before{
+    content:"\e193"
+}
+.glyphicon-copyright-mark:before{
+    content:"\e194"
+}
+.glyphicon-registration-mark:before{
+    content:"\e195"
+}
+.glyphicon-cloud-download:before{
+    content:"\e197"
+}
+.glyphicon-cloud-upload:before{
+    content:"\e198"
+}
+.glyphicon-tree-conifer:before{
+    content:"\e199"
+}
+.glyphicon-tree-deciduous:before{
+    content:"\e200"
+}
+.glyphicon-cd:before{
+    content:"\e201"
+}
+.glyphicon-save-file:before{
+    content:"\e202"
+}
+.glyphicon-open-file:before{
+    content:"\e203"
+}
+.glyphicon-level-up:before{
+    content:"\e204"
+}
+.glyphicon-copy:before{
+    content:"\e205"
+}
+.glyphicon-paste:before{
+    content:"\e206"
+}
+.glyphicon-alert:before{
+    content:"\e209"
+}
+.glyphicon-equalizer:before{
+    content:"\e210"
+}
+.glyphicon-king:before{
+    content:"\e211"
+}
+.glyphicon-queen:before{
+    content:"\e212"
+}
+.glyphicon-pawn:before{
+    content:"\e213"
+}
+.glyphicon-bishop:before{
+    content:"\e214"
+}
+.glyphicon-knight:before{
+    content:"\e215"
+}
+.glyphicon-baby-formula:before{
+    content:"\e216"
+}
+.glyphicon-tent:before{
+    content:"\26fa"
+}
+.glyphicon-blackboard:before{
+    content:"\e218"
+}
+.glyphicon-bed:before{
+    content:"\e219"
+}
+.glyphicon-apple:before{
+    content:"\f8ff"
+}
+.glyphicon-erase:before{
+    content:"\e221"
+}
+.glyphicon-hourglass:before{
+    content:"\231b"
+}
+.glyphicon-lamp:before{
+    content:"\e223"
+}
+.glyphicon-duplicate:before{
+    content:"\e224"
+}
+.glyphicon-piggy-bank:before{
+    content:"\e225"
+}
+.glyphicon-scissors:before{
+    content:"\e226"
+}
+.glyphicon-bitcoin:before{
+    content:"\e227"
+}
+.glyphicon-btc:before{
+    content:"\e227"
+}
+.glyphicon-xbt:before{
+    content:"\e227"
+}
+.glyphicon-yen:before{
+    content:"\00a5"
+}
+.glyphicon-jpy:before{
+    content:"\00a5"
+}
+.glyphicon-ruble:before{
+    content:"\20bd"
+}
+.glyphicon-rub:before{
+    content:"\20bd"
+}
+.glyphicon-scale:before{
+    content:"\e230"
+}
+.glyphicon-ice-lolly:before{
+    content:"\e231"
+}
+.glyphicon-ice-lolly-tasted:before{
+    content:"\e232"
+}
+.glyphicon-education:before{
+    content:"\e233"
+}
+.glyphicon-option-horizontal:before{
+    content:"\e234"
+}
+.glyphicon-option-vertical:before{
+    content:"\e235"
+}
+.glyphicon-menu-hamburger:before{
+    content:"\e236"
+}
+.glyphicon-modal-window:before{
+    content:"\e237"
+}
+.glyphicon-oil:before{
+    content:"\e238"
+}
+.glyphicon-grain:before{
+    content:"\e239"
+}
+.glyphicon-sunglasses:before{
+    content:"\e240"
+}
+.glyphicon-text-size:before{
+    content:"\e241"
+}
+.glyphicon-text-color:before{
+    content:"\e242"
+}
+.glyphicon-text-background:before{
+    content:"\e243"
+}
+.glyphicon-object-align-top:before{
+    content:"\e244"
+}
+.glyphicon-object-align-bottom:before{
+    content:"\e245"
+}
+.glyphicon-object-align-horizontal:before{
+    content:"\e246"
+}
+.glyphicon-object-align-left:before{
+    content:"\e247"
+}
+.glyphicon-object-align-vertical:before{
+    content:"\e248"
+}
+.glyphicon-object-align-right:before{
+    content:"\e249"
+}
+.glyphicon-triangle-right:before{
+    content:"\e250"
+}
+.glyphicon-triangle-left:before{
+    content:"\e251"
+}
+.glyphicon-triangle-bottom:before{
+    content:"\e252"
+}
+.glyphicon-triangle-top:before{
+    content:"\e253"
+}
+.glyphicon-console:before{
+    content:"\e254"
+}
+.glyphicon-superscript:before{
+    content:"\e255"
+}
+.glyphicon-subscript:before{
+    content:"\e256"
+}
+.glyphicon-menu-left:before{
+    content:"\e257"
+}
+.glyphicon-menu-right:before{
+    content:"\e258"
+}
+.glyphicon-menu-down:before{
+    content:"\e259"
+}
+.glyphicon-menu-up:before{
+    content:"\e260"
+}
+*{
+    -webkit-box-sizing:border-box;
+    -moz-box-sizing:border-box;
+    box-sizing:border-box
+}
+:after,:before{
+    -webkit-box-sizing:border-box;
+    -moz-box-sizing:border-box;
+    box-sizing:border-box
+}
+html{
+    font-size:10px;
+    -webkit-tap-highlight-color:rgba(0,0,0,0)
+}
+body{
+    font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
+    font-size:14px;
+    line-height:1.42857143;
+    color:#333;
+    background-color:#fff
+}
+button,input,select,textarea{
+    font-family:inherit;
+    font-size:inherit;
+    line-height:inherit
+}
+a{
+    color:#337ab7;
+    text-decoration:none
+}
+a:focus,a:hover{
+    color:#23527c;
+    text-decoration:underline
+}
+a:focus{
+    outline:5px auto -webkit-focus-ring-color;
+    outline-offset:-2px
+}
+figure{
+    margin:0
+}
+img{
+    vertical-align:middle
+}
+.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{
+    display:block;
+    max-width:100%;
+    height:auto
+}
+.img-rounded{
+    border-radius:6px
+}
+.img-thumbnail{
+    display:inline-block;
+    max-width:100%;
+    height:auto;
+    padding:4px;
+    line-height:1.42857143;
+    background-color:#fff;
+    border:1px solid #ddd;
+    border-radius:4px;
+    -webkit-transition:all .2s ease-in-out;
+    -o-transition:all .2s ease-in-out;
+    transition:all .2s ease-in-out
+}
+.img-circle{
+    border-radius:50%
+}
+hr{
+    margin-top:20px;
+    margin-bottom:20px;
+    border:0;
+    border-top:1px solid #eee
+}
+.sr-only{
+    position:absolute;
+    width:1px;
+    height:1px;
+    padding:0;
+    margin:-1px;
+    overflow:hidden;
+    clip:rect(0,0,0,0);
+    border:0
+}
+.sr-only-focusable:active,.sr-only-focusable:focus{
+    position:static;
+    width:auto;
+    height:auto;
+    margin:0;
+    overflow:visible;
+    clip:auto
+}
+[role=button]{
+    cursor:pointer
+}
+.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{
+    font-family:inherit;
+    font-weight:500;
+    line-height:1.1;
+    color:inherit
+}
+.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{
+    font-weight:400;
+    line-height:1;
+    color:#777
+}
+.h1,.h2,.h3,h1,h2,h3{
+    margin-top:20px;
+    margin-bottom:10px
+}
+.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{
+    font-size:65%
+}
+.h4,.h5,.h6,h4,h5,h6{
+    margin-top:10px;
+    margin-bottom:10px
+}
+.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{
+    font-size:75%
+}
+.h1,h1{
+    font-size:36px
+}
+.h2,h2{
+    font-size:30px
+}
+.h3,h3{
+    font-size:24px
+}
+.h4,h4{
+    font-size:18px
+}
+.h5,h5{
+    font-size:14px
+}
+.h6,h6{
+    font-size:12px
+}
+p{
+    margin:0 0 10px
+}
+.lead{
+    margin-bottom:20px;
+    font-size:16px;
+    font-weight:300;
+    line-height:1.4
+}
+@media (min-width:768px){
+    .lead{
+        font-size:21px
+    }
+}
+.small,small{
+    font-size:85%
+}
+.mark,mark{
+    padding:.2em;
+    background-color:#fcf8e3
+}
+.text-left{
+    text-align:left
+}
+.text-right{
+    text-align:right
+}
+.text-center{
+    text-align:center
+}
+.text-justify{
+    text-align:justify
+}
+.text-nowrap{
+    white-space:nowrap
+}
+.text-lowercase{
+    text-transform:lowercase
+}
+.text-uppercase{
+    text-transform:uppercase
+}
+.text-capitalize{
+    text-transform:capitalize
+}
+.text-muted{
+    color:#777
+}
+.text-primary{
+    color:#337ab7
+}
+a.text-primary:focus,a.text-primary:hover{
+    color:#286090
+}
+.text-success{
+    color:#3c763d
+}
+a.text-success:focus,a.text-success:hover{
+    color:#2b542c
+}
+.text-info{
+    color:#31708f
+}
+a.text-info:focus,a.text-info:hover{
+    color:#245269
+}
+.text-warning{
+    color:#8a6d3b
+}
+a.text-warning:focus,a.text-warning:hover{
+    color:#66512c
+}
+.text-danger{
+    color:#a94442
+}
+a.text-danger:focus,a.text-danger:hover{
+    color:#843534
+}
+.bg-primary{
+    color:#fff;
+    background-color:#337ab7
+}
+a.bg-primary:focus,a.bg-primary:hover{
+    background-color:#286090
+}
+.bg-success{
+    background-color:#dff0d8
+}
+a.bg-success:focus,a.bg-success:hover{
+    background-color:#c1e2b3
+}
+.bg-info{
+    background-color:#d9edf7
+}
+a.bg-info:focus,a.bg-info:hover{
+    background-color:#afd9ee
+}
+.bg-warning{
+    background-color:#fcf8e3
+}
+a.bg-warning:focus,a.bg-warning:hover{
+    background-color:#f7ecb5
+}
+.bg-danger{
+    background-color:#f2dede
+}
+a.bg-danger:focus,a.bg-danger:hover{
+    background-color:#e4b9b9
+}
+.page-header{
+    padding-bottom:9px;
+    margin:40px 0 20px;
+    border-bottom:1px solid #eee
+}
+ol,ul{
+    margin-top:0;
+    margin-bottom:10px
+}
+ol ol,ol ul,ul ol,ul ul{
+    margin-bottom:0
+}
+.list-unstyled{
+    padding-left:0;
+    list-style:none
+}
+.list-inline{
+    padding-left:0;
+    margin-left:-5px;
+    list-style:none
+}
+.list-inline>li{
+    display:inline-block;
+    padding-right:5px;
+    padding-left:5px
+}
+dl{
+    margin-top:0;
+    margin-bottom:20px
+}
+dd,dt{
+    line-height:1.42857143
+}
+dt{
+    font-weight:700
+}
+dd{
+    margin-left:0
+}
+@media (min-width:768px){
+    .dl-horizontal dt{
+        float:left;
+        width:160px;
+        overflow:hidden;
+        clear:left;
+        text-align:right;
+        text-overflow:ellipsis;
+        white-space:nowrap
+    }
+    .dl-horizontal dd{
+        margin-left:180px
+    }
+}
+abbr[data-original-title],abbr[title]{
+    cursor:help;
+    border-bottom:1px dotted #777
+}
+.initialism{
+    font-size:90%;
+    text-transform:uppercase
+}
+blockquote{
+    padding:10px 20px;
+    margin:0 0 20px;
+    font-size:17.5px;
+    border-left:5px solid #eee
+}
+blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{
+    margin-bottom:0
+}
+blockquote .small,blockquote footer,blockquote small{
+    display:block;
+    font-size:80%;
+    line-height:1.42857143;
+    color:#777
+}
+blockquote .small:before,blockquote footer:before,blockquote small:before{
+    content:'\2014 \00A0'
+}
+.blockquote-reverse,blockquote.pull-right{
+    padding-right:15px;
+    padding-left:0;
+    text-align:right;
+    border-right:5px solid #eee;
+    border-left:0
+}
+.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{
+    content:''
+}
+.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{
+    content:'\00A0 \2014'
+}
+address{
+    margin-bottom:20px;
+    font-style:normal;
+    line-height:1.42857143
+}
+code,kbd,pre,samp{
+    font-family:Menlo,Monaco,Consolas,"Courier New",monospace
+}
+code{
+    padding:2px 4px;
+    font-size:90%;
+    color:#c7254e;
+    background-color:#f9f2f4;
+    border-radius:4px
+}
+kbd{
+    padding:2px 4px;
+    font-size:90%;
+    color:#fff;
+    background-color:#333;
+    border-radius:3px;
+    -webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);
+    box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)
+}
+kbd kbd{
+    padding:0;
+    font-size:100%;
+    font-weight:700;
+    -webkit-box-shadow:none;
+    box-shadow:none
+}
+pre{
+    display:block;
+    padding:9.5px;
+    margin:0 0 10px;
+    font-size:13px;
+    line-height:1.42857143;
+    color:#333;
+    word-break:break-all;
+    word-wrap:break-word;
+    background-color:#f5f5f5;
+    border:1px solid #ccc;
+    border-radius:4px
+}
+pre code{
+    padding:0;
+    font-size:inherit;
+    color:inherit;
+    white-space:pre-wrap;
+    background-color:transparent;
+    border-radius:0
+}
+.pre-scrollable{
+    max-height:340px;
+    overflow-y:scroll
+}
+.container{
+    padding-right:15px;
+    padding-left:15px;
+    margin-right:auto;
+    margin-left:auto
+}
+@media (min-width:768px){
+    .container{
+        width:750px
+    }
+}
+@media (min-width:992px){
+    .container{
+        width:970px
+    }
+}
+@media (min-width:1200px){
+    .container{
+        width:1170px
+    }
+}
+.container-fluid{
+    padding-right:15px;
+    padding-left:15px;
+    margin-right:auto;
+    margin-left:auto
+}
+.row{
+    margin-right:-15px;
+    margin-left:-15px
+}
+.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{
+    position:relative;
+    min-height:1px;
+    padding-right:15px;
+    padding-left:15px
+}
+.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{
+    float:left
+}
+.col-xs-12{
+    width:100%
+}
+.col-xs-11{
+    width:91.66666667%
+}
+.col-xs-10{
+    width:83.33333333%
+}
+.col-xs-9{
+    width:75%
+}
+.col-xs-8{
+    width:66.66666667%
+}
+.col-xs-7{
+    width:58.33333333%
+}
+.col-xs-6{
+    width:50%
+}
+.col-xs-5{
+    width:41.66666667%
+}
+.col-xs-4{
+    width:33.33333333%
+}
+.col-xs-3{
+    width:25%
+}
+.col-xs-2{
+    width:16.66666667%
+}
+.col-xs-1{
+    width:8.33333333%
+}
+.col-xs-pull-12{
+    right:100%
+}
+.col-xs-pull-11{
+    right:91.66666667%
+}
+.col-xs-pull-10{
+    right:83.33333333%
+}
+.col-xs-pull-9{
+    right:75%
+}
+.col-xs-pull-8{
+    right:66.66666667%
+}
+.col-xs-pull-7{
+    right:58.33333333%
+}
+.col-xs-pull-6{
+    right:50%
+}
+.col-xs-pull-5{
+    right:41.66666667%
+}
+.col-xs-pull-4{
+    right:33.33333333%
+}
+.col-xs-pull-3{
+    right:25%
+}
+.col-xs-pull-2{
+    right:16.66666667%
+}
+.col-xs-pull-1{
+    right:8.33333333%
+}
+.col-xs-pull-0{
+    right:auto
+}
+.col-xs-push-12{
+    left:100%
+}
+.col-xs-push-11{
+    left:91.66666667%
+}
+.col-xs-push-10{
+    left:83.33333333%
+}
+.col-xs-push-9{
+    left:75%
+}
+.col-xs-push-8{
+    left:66.66666667%
+}
+.col-xs-push-7{
+    left:58.33333333%
+}
+.col-xs-push-6{
+    left:50%
+}
+.col-xs-push-5{
+    left:41.66666667%
+}
+.col-xs-push-4{
+    left:33.33333333%
+}
+.col-xs-push-3{
+    left:25%
+}
+.col-xs-push-2{
+    left:16.66666667%
+}
+.col-xs-push-1{
+    left:8.33333333%
+}
+.col-xs-push-0{
+    left:auto
+}
+.col-xs-offset-12{
+    margin-left:100%
+}
+.col-xs-offset-11{
+    margin-left:91.66666667%
+}
+.col-xs-offset-10{
+    margin-left:83.33333333%
+}
+.col-xs-offset-9{
+    margin-left:75%
+}
+.col-xs-offset-8{
+    margin-left:66.66666667%
+}
+.col-xs-offset-7{
+    margin-left:58.33333333%
+}
+.col-xs-offset-6{
+    margin-left:50%
+}
+.col-xs-offset-5{
+    margin-left:41.66666667%
+}
+.col-xs-offset-4{
+    margin-left:33.33333333%
+}
+.col-xs-offset-3{
+    margin-left:25%
+}
+.col-xs-offset-2{
+    margin-left:16.66666667%
+}
+.col-xs-offset-1{
+    margin-left:8.33333333%
+}
+.col-xs-offset-0{
+    margin-left:0
+}
+@media (min-width:768px){
+    .col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{
+        float:left
+    }
+    .col-sm-12{
+        width:100%
+    }
+    .col-sm-11{
+        width:91.66666667%
+    }
+    .col-sm-10{
+        width:83.33333333%
+    }
+    .col-sm-9{
+        width:75%
+    }
+    .col-sm-8{
+        width:66.66666667%
+    }
+    .col-sm-7{
+        width:58.33333333%
+    }
+    .col-sm-6{
+        width:50%
+    }
+    .col-sm-5{
+        width:41.66666667%
+    }
+    .col-sm-4{
+        width:33.33333333%
+    }
+    .col-sm-3{
+        width:25%
+    }
+    .col-sm-2{
+        width:16.66666667%
+    }
+    .col-sm-1{
+        width:8.33333333%
+    }
+    .col-sm-pull-12{
+        right:100%
+    }
+    .col-sm-pull-11{
+        right:91.66666667%
+    }
+    .col-sm-pull-10{
+        right:83.33333333%
+    }
+    .col-sm-pull-9{
+        right:75%
+    }
+    .col-sm-pull-8{
+        right:66.66666667%
+    }
+    .col-sm-pull-7{
+        right:58.33333333%
+    }
+    .col-sm-pull-6{
+        right:50%
+    }
+    .col-sm-pull-5{
+        right:41.66666667%
+    }
+    .col-sm-pull-4{
+        right:33.33333333%
+    }
+    .col-sm-pull-3{
+        right:25%
+    }
+    .col-sm-pull-2{
+        right:16.66666667%
+    }
+    .col-sm-pull-1{
+        right:8.33333333%
+    }
+    .col-sm-pull-0{
+        right:auto
+    }
+    .col-sm-push-12{
+        left:100%
+    }
+    .col-sm-push-11{
+        left:91.66666667%
+    }
+    .col-sm-push-10{
+        left:83.33333333%
+    }
+    .col-sm-push-9{
+        left:75%
+    }
+    .col-sm-push-8{
+        left:66.66666667%
+    }
+    .col-sm-push-7{
+        left:58.33333333%
+    }
+    .col-sm-push-6{
+        left:50%
+    }
+    .col-sm-push-5{
+        left:41.66666667%
+    }
+    .col-sm-push-4{
+        left:33.33333333%
+    }
+    .col-sm-push-3{
+        left:25%
+    }
+    .col-sm-push-2{
+        left:16.66666667%
+    }
+    .col-sm-push-1{
+        left:8.33333333%
+    }
+    .col-sm-push-0{
+        left:auto
+    }
+    .col-sm-offset-12{
+        margin-left:100%
+    }
+    .col-sm-offset-11{
+        margin-left:91.66666667%
+    }
+    .col-sm-offset-10{
+        margin-left:83.33333333%
+    }
+    .col-sm-offset-9{
+        margin-left:75%
+    }
+    .col-sm-offset-8{
+        margin-left:66.66666667%
+    }
+    .col-sm-offset-7{
+        margin-left:58.33333333%
+    }
+    .col-sm-offset-6{
+        margin-left:50%
+    }
+    .col-sm-offset-5{
+        margin-left:41.66666667%
+    }
+    .col-sm-offset-4{
+        margin-left:33.33333333%
+    }
+    .col-sm-offset-3{
+        margin-left:25%
+    }
+    .col-sm-offset-2{
+        margin-left:16.66666667%
+    }
+    .col-sm-offset-1{
+        margin-left:8.33333333%
+    }
+    .col-sm-offset-0{
+        margin-left:0
+    }
+}
+@media (min-width:992px){
+    .col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{
+        float:left
+    }
+    .col-md-12{
+        width:100%
+    }
+    .col-md-11{
+        width:91.66666667%
+    }
+    .col-md-10{
+        width:83.33333333%
+    }
+    .col-md-9{
+        width:75%
+    }
+    .col-md-8{
+        width:66.66666667%
+    }
+    .col-md-7{
+        width:58.33333333%
+    }
+    .col-md-6{
+        width:50%
+    }
+    .col-md-5{
+        width:41.66666667%
+    }
+    .col-md-4{
+        width:33.33333333%
+    }
+    .col-md-3{
+        width:25%
+    }
+    .col-md-2{
+        width:16.66666667%
+    }
+    .col-md-1{
+        width:8.33333333%
+    }
+    .col-md-pull-12{
+        right:100%
+    }
+    .col-md-pull-11{
+        right:91.66666667%
+    }
+    .col-md-pull-10{
+        right:83.33333333%
+    }
+    .col-md-pull-9{
+        right:75%
+    }
+    .col-md-pull-8{
+        right:66.66666667%
+    }
+    .col-md-pull-7{
+        right:58.33333333%
+    }
+    .col-md-pull-6{
+        right:50%
+    }
+    .col-md-pull-5{
+        right:41.66666667%
+    }
+    .col-md-pull-4{
+        right:33.33333333%
+    }
+    .col-md-pull-3{
+        right:25%
+    }
+    .col-md-pull-2{
+        right:16.66666667%
+    }
+    .col-md-pull-1{
+        right:8.33333333%
+    }
+    .col-md-pull-0{
+        right:auto
+    }
+    .col-md-push-12{
+        left:100%
+    }
+    .col-md-push-11{
+        left:91.66666667%
+    }
+    .col-md-push-10{
+        left:83.33333333%
+    }
+    .col-md-push-9{
+        left:75%
+    }
+    .col-md-push-8{
+        left:66.66666667%
+    }
+    .col-md-push-7{
+        left:58.33333333%
+    }
+    .col-md-push-6{
+        left:50%
+    }
+    .col-md-push-5{
+        left:41.66666667%
+    }
+    .col-md-push-4{
+        left:33.33333333%
+    }
+    .col-md-push-3{
+        left:25%
+    }
+    .col-md-push-2{
+        left:16.66666667%
+    }
+    .col-md-push-1{
+        left:8.33333333%
+    }
+    .col-md-push-0{
+        left:auto
+    }
+    .col-md-offset-12{
+        margin-left:100%
+    }
+    .col-md-offset-11{
+        margin-left:91.66666667%
+    }
+    .col-md-offset-10{
+        margin-left:83.33333333%
+    }
+    .col-md-offset-9{
+        margin-left:75%
+    }
+    .col-md-offset-8{
+        margin-left:66.66666667%
+    }
+    .col-md-offset-7{
+        margin-left:58.33333333%
+    }
+    .col-md-offset-6{
+        margin-left:50%
+    }
+    .col-md-offset-5{
+        margin-left:41.66666667%
+    }
+    .col-md-offset-4{
+        margin-left:33.33333333%
+    }
+    .col-md-offset-3{
+        margin-left:25%
+    }
+    .col-md-offset-2{
+        margin-left:16.66666667%
+    }
+    .col-md-offset-1{
+        margin-left:8.33333333%
+    }
+    .col-md-offset-0{
+        margin-left:0
+    }
+}
+@media (min-width:1200px){
+    .col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{
+        float:left
+    }
+    .col-lg-12{
+        width:100%
+    }
+    .col-lg-11{
+        width:91.66666667%
+    }
+    .col-lg-10{
+        width:83.33333333%
+    }
+    .col-lg-9{
+        width:75%
+    }
+    .col-lg-8{
+        width:66.66666667%
+    }
+    .col-lg-7{
+        width:58.33333333%
+    }
+    .col-lg-6{
+        width:50%
+    }
+    .col-lg-5{
+        width:41.66666667%
+    }
+    .col-lg-4{
+        width:33.33333333%
+    }
+    .col-lg-3{
+        width:25%
+    }
+    .col-lg-2{
+        width:16.66666667%
+    }
+    .col-lg-1{
+        width:8.33333333%
+    }
+    .col-lg-pull-12{
+        right:100%
+    }
+    .col-lg-pull-11{
+        right:91.66666667%
+    }
+    .col-lg-pull-10{
+        right:83.33333333%
+    }
+    .col-lg-pull-9{
+        right:75%
+    }
+    .col-lg-pull-8{
+        right:66.66666667%
+    }
+    .col-lg-pull-7{
+        right:58.33333333%
+    }
+    .col-lg-pull-6{
+        right:50%
+    }
+    .col-lg-pull-5{
+        right:41.66666667%
+    }
+    .col-lg-pull-4{
+        right:33.33333333%
+    }
+    .col-lg-pull-3{
+        right:25%
+    }
+    .col-lg-pull-2{
+        right:16.66666667%
+    }
+    .col-lg-pull-1{
+        right:8.33333333%
+    }
+    .col-lg-pull-0{
+        right:auto
+    }
+    .col-lg-push-12{
+        left:100%
+    }
+    .col-lg-push-11{
+        left:91.66666667%
+    }
+    .col-lg-push-10{
+        left:83.33333333%
+    }
+    .col-lg-push-9{
+        left:75%
+    }
+    .col-lg-push-8{
+        left:66.66666667%
+    }
+    .col-lg-push-7{
+        left:58.33333333%
+    }
+    .col-lg-push-6{
+        left:50%
+    }
+    .col-lg-push-5{
+        left:41.66666667%
+    }
+    .col-lg-push-4{
+        left:33.33333333%
+    }
+    .col-lg-push-3{
+        left:25%
+    }
+    .col-lg-push-2{
+        left:16.66666667%
+    }
+    .col-lg-push-1{
+        left:8.33333333%
+    }
+    .col-lg-push-0{
+        left:auto
+    }
+    .col-lg-offset-12{
+        margin-left:100%
+    }
+    .col-lg-offset-11{
+        margin-left:91.66666667%
+    }
+    .col-lg-offset-10{
+        margin-left:83.33333333%
+    }
+    .col-lg-offset-9{
+        margin-left:75%
+    }
+    .col-lg-offset-8{
+        margin-left:66.66666667%
+    }
+    .col-lg-offset-7{
+        margin-left:58.33333333%
+    }
+    .col-lg-offset-6{
+        margin-left:50%
+    }
+    .col-lg-offset-5{
+        margin-left:41.66666667%
+    }
+    .col-lg-offset-4{
+        margin-left:33.33333333%
+    }
+    .col-lg-offset-3{
+        margin-left:25%
+    }
+    .col-lg-offset-2{
+        margin-left:16.66666667%
+    }
+    .col-lg-offset-1{
+        margin-left:8.33333333%
+    }
+    .col-lg-offset-0{
+        margin-left:0
+    }
+}
+table{
+    background-color:transparent
+}
+caption{
+    padding-top:8px;
+    padding-bottom:8px;
+    color:#777;
+    text-align:left
+}
+th{
+    text-align:left
+}
+.table{
+    width:100%;
+    max-width:100%;
+    margin-bottom:20px
+}
+.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{
+    padding:8px;
+    line-height:1.42857143;
+    vertical-align:top;
+    border-top:1px solid #ddd
+}
+.table>thead>tr>th{
+    vertical-align:bottom;
+    border-bottom:2px solid #ddd
+}
+.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{
+    border-top:0
+}
+.table>tbody+tbody{
+    border-top:2px solid #ddd
+}
+.table .table{
+    background-color:#fff
+}
+.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{
+    padding:5px
+}
+.table-bordered{
+    border:1px solid #ddd
+}
+.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{
+    border:1px solid #ddd
+}
+.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{
+    border-bottom-width:2px
+}
+.table-striped>tbody>tr:nth-of-type(odd){
+    background-color:#f9f9f9
+}
+.table-hover>tbody>tr:hover{
+    background-color:#f5f5f5
+}
+table col[class*=col-]{
+    position:static;
+    display:table-column;
+    float:none
+}
+table td[class*=col-],table th[class*=col-]{
+    position:static;
+    display:table-cell;
+    float:none
+}
+.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{
+    background-color:#f5f5f5
+}
+.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{
+    background-color:#e8e8e8
+}
+.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{
+    background-color:#dff0d8
+}
+.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{
+    background-color:#d0e9c6
+}
+.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{
+    background-color:#d9edf7
+}
+.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{
+    background-color:#c4e3f3
+}
+.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{
+    background-color:#fcf8e3
+}
+.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{
+    background-color:#faf2cc
+}
+.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{
+    background-color:#f2dede
+}
+.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{
+    background-color:#ebcccc
+}
+.table-responsive{
+    min-height:.01%;
+    overflow-x:auto
+}
+@media screen and (max-width:767px){
+    .table-responsive{
+        width:100%;
+        margin-bottom:15px;
+        overflow-y:hidden;
+        -ms-overflow-style:-ms-autohiding-scrollbar;
+        border:1px solid #ddd
+    }
+    .table-responsive>.table{
+        margin-bottom:0
+    }
+    .table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{
+        white-space:nowrap
+    }
+    .table-responsive>.table-bordered{
+        border:0
+    }
+    .table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{
+        border-left:0
+    }
+    .table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{
+        border-right:0
+    }
+    .table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{
+        border-bottom:0
+    }
+}
+fieldset{
+    min-width:0;
+    padding:0;
+    margin:0;
+    border:0
+}
+legend{
+    display:block;
+    width:100%;
+    padding:0;
+    margin-bottom:20px;
+    font-size:21px;
+    line-height:inherit;
+    color:#333;
+    border:0;
+    border-bottom:1px solid #e5e5e5
+}
+label{
+    display:inline-block;
+    max-width:100%;
+    margin-bottom:5px;
+    font-weight:700
+}
+input[type=search]{
+    -webkit-box-sizing:border-box;
+    -moz-box-sizing:border-box;
+    box-sizing:border-box
+}
+input[type=checkbox],input[type=radio]{
+    margin:4px 0 0;
+    margin-top:1px\9;
+    line-height:normal
+}
+input[type=file]{
+    display:block
+}
+input[type=range]{
+    display:block;
+    width:100%
+}
+select[multiple],select[size]{
+    height:auto
+}
+input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{
+    outline:5px auto -webkit-focus-ring-color;
+    outline-offset:-2px
+}
+output{
+    display:block;
+    padding-top:7px;
+    font-size:14px;
+    line-height:1.42857143;
+    color:#555
+}
+.form-control{
+    display:block;
+    width:100%;
+    height:34px;
+    padding:6px 12px;
+    font-size:14px;
+    line-height:1.42857143;
+    color:#555;
+    background-color:#fff;
+    background-image:none;
+    border:1px solid #ccc;
+    border-radius:4px;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075);
+    -webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
+    -o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;
+    transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s
+}
+.form-control:focus{
+    border-color:#66afe9;
+    outline:0;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)
+}
+.form-control::-moz-placeholder{
+    color:#999;
+    opacity:1
+}
+.form-control:-ms-input-placeholder{
+    color:#999
+}
+.form-control::-webkit-input-placeholder{
+    color:#999
+}
+.form-control::-ms-expand{
+    background-color:transparent;
+    border:0
+}
+.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{
+    background-color:#eee;
+    opacity:1
+}
+.form-control[disabled],fieldset[disabled] .form-control{
+    cursor:not-allowed
+}
+textarea.form-control{
+    height:auto
+}
+input[type=search]{
+    -webkit-appearance:none
+}
+@media screen and (-webkit-min-device-pixel-ratio:0){
+    input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{
+        line-height:34px
+    }
+    .input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{
+        line-height:30px
+    }
+    .input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{
+        line-height:46px
+    }
+}
+.form-group{
+    margin-bottom:15px
+}
+.checkbox,.radio{
+    position:relative;
+    display:block;
+    margin-top:10px;
+    margin-bottom:10px
+}
+.checkbox label,.radio label{
+    min-height:20px;
+    padding-left:20px;
+    margin-bottom:0;
+    font-weight:400;
+    cursor:pointer
+}
+.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{
+    position:absolute;
+    margin-top:4px\9;
+    margin-left:-20px
+}
+.checkbox+.checkbox,.radio+.radio{
+    margin-top:-5px
+}
+.checkbox-inline,.radio-inline{
+    position:relative;
+    display:inline-block;
+    padding-left:20px;
+    margin-bottom:0;
+    font-weight:400;
+    vertical-align:middle;
+    cursor:pointer
+}
+.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{
+    margin-top:0;
+    margin-left:10px
+}
+fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{
+    cursor:not-allowed
+}
+.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{
+    cursor:not-allowed
+}
+.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{
+    cursor:not-allowed
+}
+.form-control-static{
+    min-height:34px;
+    padding-top:7px;
+    padding-bottom:7px;
+    margin-bottom:0
+}
+.form-control-static.input-lg,.form-control-static.input-sm{
+    padding-right:0;
+    padding-left:0
+}
+.input-sm{
+    height:30px;
+    padding:5px 10px;
+    font-size:12px;
+    line-height:1.5;
+    border-radius:3px
+}
+select.input-sm{
+    height:30px;
+    line-height:30px
+}
+select[multiple].input-sm,textarea.input-sm{
+    height:auto
+}
+.form-group-sm .form-control{
+    height:30px;
+    padding:5px 10px;
+    font-size:12px;
+    line-height:1.5;
+    border-radius:3px
+}
+.form-group-sm select.form-control{
+    height:30px;
+    line-height:30px
+}
+.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{
+    height:auto
+}
+.form-group-sm .form-control-static{
+    height:30px;
+    min-height:32px;
+    padding:6px 10px;
+    font-size:12px;
+    line-height:1.5
+}
+.input-lg{
+    height:46px;
+    padding:10px 16px;
+    font-size:18px;
+    line-height:1.3333333;
+    border-radius:6px
+}
+select.input-lg{
+    height:46px;
+    line-height:46px
+}
+select[multiple].input-lg,textarea.input-lg{
+    height:auto
+}
+.form-group-lg .form-control{
+    height:46px;
+    padding:10px 16px;
+    font-size:18px;
+    line-height:1.3333333;
+    border-radius:6px
+}
+.form-group-lg select.form-control{
+    height:46px;
+    line-height:46px
+}
+.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{
+    height:auto
+}
+.form-group-lg .form-control-static{
+    height:46px;
+    min-height:38px;
+    padding:11px 16px;
+    font-size:18px;
+    line-height:1.3333333
+}
+.has-feedback{
+    position:relative
+}
+.has-feedback .form-control{
+    padding-right:42.5px
+}
+.form-control-feedback{
+    position:absolute;
+    top:0;
+    right:0;
+    z-index:2;
+    display:block;
+    width:34px;
+    height:34px;
+    line-height:34px;
+    text-align:center;
+    pointer-events:none
+}
+.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{
+    width:46px;
+    height:46px;
+    line-height:46px
+}
+.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{
+    width:30px;
+    height:30px;
+    line-height:30px
+}
+.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{
+    color:#3c763d
+}
+.has-success .form-control{
+    border-color:#3c763d;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075)
+}
+.has-success .form-control:focus{
+    border-color:#2b542c;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168
+}
+.has-success .input-group-addon{
+    color:#3c763d;
+    background-color:#dff0d8;
+    border-color:#3c763d
+}
+.has-success .form-control-feedback{
+    color:#3c763d
+}
+.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{
+    color:#8a6d3b
+}
+.has-warning .form-control{
+    border-color:#8a6d3b;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075)
+}
+.has-warning .form-control:focus{
+    border-color:#66512c;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b
+}
+.has-warning .input-group-addon{
+    color:#8a6d3b;
+    background-color:#fcf8e3;
+    border-color:#8a6d3b
+}
+.has-warning .form-control-feedback{
+    color:#8a6d3b
+}
+.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{
+    color:#a94442
+}
+.has-error .form-control{
+    border-color:#a94442;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075)
+}
+.has-error .form-control:focus{
+    border-color:#843534;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483
+}
+.has-error .input-group-addon{
+    color:#a94442;
+    background-color:#f2dede;
+    border-color:#a94442
+}
+.has-error .form-control-feedback{
+    color:#a94442
+}
+.has-feedback label~.form-control-feedback{
+    top:25px
+}
+.has-feedback label.sr-only~.form-control-feedback{
+    top:0
+}
+.help-block{
+    display:block;
+    margin-top:5px;
+    margin-bottom:10px;
+    color:#737373
+}
+@media (min-width:768px){
+    .form-inline .form-group{
+        display:inline-block;
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .form-inline .form-control{
+        display:inline-block;
+        width:auto;
+        vertical-align:middle
+    }
+    .form-inline .form-control-static{
+        display:inline-block
+    }
+    .form-inline .input-group{
+        display:inline-table;
+        vertical-align:middle
+    }
+    .form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{
+        width:auto
+    }
+    .form-inline .input-group>.form-control{
+        width:100%
+    }
+    .form-inline .control-label{
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .form-inline .checkbox,.form-inline .radio{
+        display:inline-block;
+        margin-top:0;
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .form-inline .checkbox label,.form-inline .radio label{
+        padding-left:0
+    }
+    .form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{
+        position:relative;
+        margin-left:0
+    }
+    .form-inline .has-feedback .form-control-feedback{
+        top:0
+    }
+}
+.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{
+    padding-top:7px;
+    margin-top:0;
+    margin-bottom:0
+}
+.form-horizontal .checkbox,.form-horizontal .radio{
+    min-height:27px
+}
+.form-horizontal .form-group{
+    margin-right:-15px;
+    margin-left:-15px
+}
+@media (min-width:768px){
+    .form-horizontal .control-label{
+        padding-top:7px;
+        margin-bottom:0;
+        text-align:right
+    }
+}
+.form-horizontal .has-feedback .form-control-feedback{
+    right:15px
+}
+@media (min-width:768px){
+    .form-horizontal .form-group-lg .control-label{
+        padding-top:11px;
+        font-size:18px
+    }
+}
+@media (min-width:768px){
+    .form-horizontal .form-group-sm .control-label{
+        padding-top:6px;
+        font-size:12px
+    }
+}
+.btn{
+    display:inline-block;
+    padding:6px 12px;
+    margin-bottom:0;
+    font-size:14px;
+    font-weight:400;
+    line-height:1.42857143;
+    text-align:center;
+    white-space:nowrap;
+    vertical-align:middle;
+    -ms-touch-action:manipulation;
+    touch-action:manipulation;
+    cursor:pointer;
+    -webkit-user-select:none;
+    -moz-user-select:none;
+    -ms-user-select:none;
+    user-select:none;
+    background-image:none;
+    border:1px solid transparent;
+    border-radius:4px
+}
+.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{
+    outline:5px auto -webkit-focus-ring-color;
+    outline-offset:-2px
+}
+.btn.focus,.btn:focus,.btn:hover{
+    color:#333;
+    text-decoration:none
+}
+.btn.active,.btn:active{
+    background-image:none;
+    outline:0;
+    -webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);
+    box-shadow:inset 0 3px 5px rgba(0,0,0,.125)
+}
+.btn.disabled,.btn[disabled],fieldset[disabled] .btn{
+    cursor:not-allowed;
+    filter:alpha(opacity=65);
+    -webkit-box-shadow:none;
+    box-shadow:none;
+    opacity:.65
+}
+a.btn.disabled,fieldset[disabled] a.btn{
+    pointer-events:none
+}
+.btn-default{
+    color:#333;
+    background-color:#fff;
+    border-color:#ccc
+}
+.btn-default.focus,.btn-default:focus{
+    color:#333;
+    background-color:#e6e6e6;
+    border-color:#8c8c8c
+}
+.btn-default:hover{
+    color:#333;
+    background-color:#e6e6e6;
+    border-color:#adadad
+}
+.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{
+    color:#333;
+    background-color:#e6e6e6;
+    border-color:#adadad
+}
+.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{
+    color:#333;
+    background-color:#d4d4d4;
+    border-color:#8c8c8c
+}
+.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{
+    background-image:none
+}
+.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{
+    background-color:#fff;
+    border-color:#ccc
+}
+.btn-default .badge{
+    color:#fff;
+    background-color:#333
+}
+.btn-primary{
+    color:#fff;
+    background-color:#d11010;
+    border-color:#c40f0f
+}
+.btn-primary.focus,.btn-primary:focus{
+    color:#fff;
+    background-color:#b20c0c;
+    border-color:#c40f0f
+}
+.btn-primary:hover{
+    color:#fff;
+    background-color:#b20c0c;
+    border-color:#c40f0f
+}
+.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{
+    color:#fff;
+    background-color:#b20c0c;
+    border-color:#c40f0f
+}
+.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{
+    color:#fff;
+    background-color:#b20c0c;
+    border-color:#c40f0f
+}
+.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{
+    background-image:none
+}
+.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{
+    background-color:#b20c0c;
+    border-color:#c40f0f
+}
+.btn-primary .badge{
+    color:#337ab7;
+    background-color:#fff
+}
+.btn-success{
+    color:#fff;
+    background-color:#5cb85c;
+    border-color:#4cae4c
+}
+.btn-success.focus,.btn-success:focus{
+    color:#fff;
+    background-color:#449d44;
+    border-color:#255625
+}
+.btn-success:hover{
+    color:#fff;
+    background-color:#449d44;
+    border-color:#398439
+}
+.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{
+    color:#fff;
+    background-color:#449d44;
+    border-color:#398439
+}
+.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{
+    color:#fff;
+    background-color:#398439;
+    border-color:#255625
+}
+.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{
+    background-image:none
+}
+.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{
+    background-color:#5cb85c;
+    border-color:#4cae4c
+}
+.btn-success .badge{
+    color:#5cb85c;
+    background-color:#fff
+}
+.btn-info{
+    color:#fff;
+    background-color:#5bc0de;
+    border-color:#46b8da
+}
+.btn-info.focus,.btn-info:focus{
+    color:#fff;
+    background-color:#31b0d5;
+    border-color:#1b6d85
+}
+.btn-info:hover{
+    color:#fff;
+    background-color:#31b0d5;
+    border-color:#269abc
+}
+.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{
+    color:#fff;
+    background-color:#31b0d5;
+    border-color:#269abc
+}
+.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{
+    color:#fff;
+    background-color:#269abc;
+    border-color:#1b6d85
+}
+.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{
+    background-image:none
+}
+.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{
+    background-color:#5bc0de;
+    border-color:#46b8da
+}
+.btn-info .badge{
+    color:#5bc0de;
+    background-color:#fff
+}
+.btn-warning{
+    color:#fff;
+    background-color:#f0ad4e;
+    border-color:#eea236
+}
+.btn-warning.focus,.btn-warning:focus{
+    color:#fff;
+    background-color:#ec971f;
+    border-color:#985f0d
+}
+.btn-warning:hover{
+    color:#fff;
+    background-color:#ec971f;
+    border-color:#d58512
+}
+.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{
+    color:#fff;
+    background-color:#ec971f;
+    border-color:#d58512
+}
+.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{
+    color:#fff;
+    background-color:#d58512;
+    border-color:#985f0d
+}
+.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{
+    background-image:none
+}
+.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{
+    background-color:#f0ad4e;
+    border-color:#eea236
+}
+.btn-warning .badge{
+    color:#f0ad4e;
+    background-color:#fff
+}
+.btn-danger{
+    color:#fff;
+    background-color:#d9534f;
+    border-color:#d43f3a
+}
+.btn-danger.focus,.btn-danger:focus{
+    color:#fff;
+    background-color:#c9302c;
+    border-color:#761c19
+}
+.btn-danger:hover{
+    color:#fff;
+    background-color:#c9302c;
+    border-color:#ac2925
+}
+.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{
+    color:#fff;
+    background-color:#c9302c;
+    border-color:#ac2925
+}
+.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{
+    color:#fff;
+    background-color:#ac2925;
+    border-color:#761c19
+}
+.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{
+    background-image:none
+}
+.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{
+    background-color:#d9534f;
+    border-color:#d43f3a
+}
+.btn-danger .badge{
+    color:#d9534f;
+    background-color:#fff
+}
+.btn-link{
+    font-weight:400;
+    color:#337ab7;
+    border-radius:0
+}
+.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{
+    background-color:transparent;
+    -webkit-box-shadow:none;
+    box-shadow:none
+}
+.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{
+    border-color:transparent
+}
+.btn-link:focus,.btn-link:hover{
+    color:#23527c;
+    text-decoration:underline;
+    background-color:transparent
+}
+.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{
+    color:#777;
+    text-decoration:none
+}
+.btn-group-lg>.btn,.btn-lg{
+    padding:10px 16px;
+    font-size:18px;
+    line-height:1.3333333;
+    border-radius:6px
+}
+.btn-group-sm>.btn,.btn-sm{
+    padding:5px 10px;
+    font-size:12px;
+    line-height:1.5;
+    border-radius:3px
+}
+.btn-group-xs>.btn,.btn-xs{
+    padding:1px 5px;
+    font-size:12px;
+    line-height:1.5;
+    border-radius:3px
+}
+.btn-block{
+    display:block;
+    width:100%
+}
+.btn-block+.btn-block{
+    margin-top:5px
+}
+input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{
+    width:100%
+}
+.fade{
+    opacity:0;
+    -webkit-transition:opacity .15s linear;
+    -o-transition:opacity .15s linear;
+    transition:opacity .15s linear
+}
+.fade.in{
+    opacity:1
+}
+.collapse{
+    display:none
+}
+.collapse.in{
+    display:block
+}
+tr.collapse.in{
+    display:table-row
+}
+tbody.collapse.in{
+    display:table-row-group
+}
+.collapsing{
+    position:relative;
+    height:0;
+    overflow:hidden;
+    -webkit-transition-timing-function:ease;
+    -o-transition-timing-function:ease;
+    transition-timing-function:ease;
+    -webkit-transition-duration:.35s;
+    -o-transition-duration:.35s;
+    transition-duration:.35s;
+    -webkit-transition-property:height,visibility;
+    -o-transition-property:height,visibility;
+    transition-property:height,visibility
+}
+.caret{
+    display:inline-block;
+    width:0;
+    height:0;
+    margin-left:2px;
+    vertical-align:middle;
+    border-top:4px dashed;
+    border-top:4px solid\9;
+    border-right:4px solid transparent;
+    border-left:4px solid transparent
+}
+.dropdown,.dropup{
+    position:relative
+}
+.dropdown-toggle:focus{
+    outline:0
+}
+.dropdown-menu{
+    position:absolute;
+    top:100%;
+    left:0;
+    z-index:1000;
+    display:none;
+    float:left;
+    min-width:160px;
+    padding:5px 0;
+    margin:2px 0 0;
+    font-size:14px;
+    text-align:left;
+    list-style:none;
+    background-color:#fff;
+    -webkit-background-clip:padding-box;
+    background-clip:padding-box;
+    border:1px solid #ccc;
+    border:1px solid rgba(0,0,0,.15);
+    border-radius:4px;
+    -webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);
+    box-shadow:0 6px 12px rgba(0,0,0,.175)
+}
+.dropdown-menu.pull-right{
+    right:0;
+    left:auto
+}
+.dropdown-menu .divider{
+    height:1px;
+    margin:9px 0;
+    overflow:hidden;
+    background-color:#e5e5e5
+}
+.dropdown-menu>li>a{
+    display:block;
+    padding:3px 20px;
+    clear:both;
+    font-weight:400;
+    line-height:1.42857143;
+    color:#333;
+    white-space:nowrap
+}
+.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{
+    color:#262626;
+    text-decoration:none;
+    background-color:#f5f5f5
+}
+.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{
+    color:#fff;
+    text-decoration:none;
+    background-color:#337ab7;
+    outline:0
+}
+.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{
+    color:#777
+}
+.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{
+    text-decoration:none;
+    cursor:not-allowed;
+    background-color:transparent;
+    background-image:none;
+    filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)
+}
+.open>.dropdown-menu{
+    display:block
+}
+.open>a{
+    outline:0
+}
+.dropdown-menu-right{
+    right:0;
+    left:auto
+}
+.dropdown-menu-left{
+    right:auto;
+    left:0
+}
+.dropdown-header{
+    display:block;
+    padding:3px 20px;
+    font-size:12px;
+    line-height:1.42857143;
+    color:#777;
+    white-space:nowrap
+}
+.dropdown-backdrop{
+    position:fixed;
+    top:0;
+    right:0;
+    bottom:0;
+    left:0;
+    z-index:990
+}
+.pull-right>.dropdown-menu{
+    right:0;
+    left:auto
+}
+.dropup .caret,.navbar-fixed-bottom .dropdown .caret{
+    content:"";
+    border-top:0;
+    border-bottom:4px dashed;
+    border-bottom:4px solid\9
+}
+.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{
+    top:auto;
+    bottom:100%;
+    margin-bottom:2px
+}
+@media (min-width:768px){
+    .navbar-right .dropdown-menu{
+        right:0;
+        left:auto
+    }
+    .navbar-right .dropdown-menu-left{
+        right:auto;
+        left:0
+    }
+}
+.btn-group,.btn-group-vertical{
+    position:relative;
+    display:inline-block;
+    vertical-align:middle
+}
+.btn-group-vertical>.btn,.btn-group>.btn{
+    position:relative;
+    float:left
+}
+.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{
+    z-index:2
+}
+.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{
+    margin-left:-1px
+}
+.btn-toolbar{
+    margin-left:-5px
+}
+.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{
+    float:left
+}
+.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{
+    margin-left:5px
+}
+.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){
+    border-radius:0
+}
+.btn-group>.btn:first-child{
+    margin-left:0
+}
+.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){
+    border-top-right-radius:0;
+    border-bottom-right-radius:0
+}
+.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){
+    border-top-left-radius:0;
+    border-bottom-left-radius:0
+}
+.btn-group>.btn-group{
+    float:left
+}
+.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{
+    border-radius:0
+}
+.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{
+    border-top-right-radius:0;
+    border-bottom-right-radius:0
+}
+.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{
+    border-top-left-radius:0;
+    border-bottom-left-radius:0
+}
+.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{
+    outline:0
+}
+.btn-group>.btn+.dropdown-toggle{
+    padding-right:8px;
+    padding-left:8px
+}
+.btn-group>.btn-lg+.dropdown-toggle{
+    padding-right:12px;
+    padding-left:12px
+}
+.btn-group.open .dropdown-toggle{
+    -webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);
+    box-shadow:inset 0 3px 5px rgba(0,0,0,.125)
+}
+.btn-group.open .dropdown-toggle.btn-link{
+    -webkit-box-shadow:none;
+    box-shadow:none
+}
+.btn .caret{
+    margin-left:0
+}
+.btn-lg .caret{
+    border-width:5px 5px 0;
+    border-bottom-width:0
+}
+.dropup .btn-lg .caret{
+    border-width:0 5px 5px
+}
+.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{
+    display:block;
+    float:none;
+    width:100%;
+    max-width:100%
+}
+.btn-group-vertical>.btn-group>.btn{
+    float:none
+}
+.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{
+    margin-top:-1px;
+    margin-left:0
+}
+.btn-group-vertical>.btn:not(:first-child):not(:last-child){
+    border-radius:0
+}
+.btn-group-vertical>.btn:first-child:not(:last-child){
+    border-top-left-radius:4px;
+    border-top-right-radius:4px;
+    border-bottom-right-radius:0;
+    border-bottom-left-radius:0
+}
+.btn-group-vertical>.btn:last-child:not(:first-child){
+    border-top-left-radius:0;
+    border-top-right-radius:0;
+    border-bottom-right-radius:4px;
+    border-bottom-left-radius:4px
+}
+.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{
+    border-radius:0
+}
+.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{
+    border-bottom-right-radius:0;
+    border-bottom-left-radius:0
+}
+.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{
+    border-top-left-radius:0;
+    border-top-right-radius:0
+}
+.btn-group-justified{
+    display:table;
+    width:100%;
+    table-layout:fixed;
+    border-collapse:separate
+}
+.btn-group-justified>.btn,.btn-group-justified>.btn-group{
+    display:table-cell;
+    float:none;
+    width:1%
+}
+.btn-group-justified>.btn-group .btn{
+    width:100%
+}
+.btn-group-justified>.btn-group .dropdown-menu{
+    left:auto
+}
+[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{
+    position:absolute;
+    clip:rect(0,0,0,0);
+    pointer-events:none
+}
+.input-group{
+    position:relative;
+    display:table;
+    border-collapse:separate
+}
+.input-group[class*=col-]{
+    float:none;
+    padding-right:0;
+    padding-left:0
+}
+.input-group .form-control{
+    position:relative;
+    z-index:2;
+    float:left;
+    width:100%;
+    margin-bottom:0
+}
+.input-group .form-control:focus{
+    z-index:3
+}
+.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{
+    height:46px;
+    padding:10px 16px;
+    font-size:18px;
+    line-height:1.3333333;
+    border-radius:6px
+}
+select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{
+    height:46px;
+    line-height:46px
+}
+select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{
+    height:auto
+}
+.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{
+    height:30px;
+    padding:5px 10px;
+    font-size:12px;
+    line-height:1.5;
+    border-radius:3px
+}
+select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{
+    height:30px;
+    line-height:30px
+}
+select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{
+    height:auto
+}
+.input-group .form-control,.input-group-addon,.input-group-btn{
+    display:table-cell
+}
+.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){
+    border-radius:0
+}
+.input-group-addon,.input-group-btn{
+    width:1%;
+    white-space:nowrap;
+    vertical-align:middle
+}
+.input-group-addon{
+    padding:6px 12px;
+    font-size:14px;
+    font-weight:400;
+    line-height:1;
+    color:#555;
+    text-align:center;
+    background-color:#eee;
+    border:1px solid #ccc;
+    border-radius:4px
+}
+.input-group-addon.input-sm{
+    padding:5px 10px;
+    font-size:12px;
+    border-radius:3px
+}
+.input-group-addon.input-lg{
+    padding:10px 16px;
+    font-size:18px;
+    border-radius:6px
+}
+.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{
+    margin-top:0
+}
+.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){
+    border-top-right-radius:0;
+    border-bottom-right-radius:0
+}
+.input-group-addon:first-child{
+    border-right:0
+}
+.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{
+    border-top-left-radius:0;
+    border-bottom-left-radius:0
+}
+.input-group-addon:last-child{
+    border-left:0
+}
+.input-group-btn{
+    position:relative;
+    font-size:0;
+    white-space:nowrap
+}
+.input-group-btn>.btn{
+    position:relative
+}
+.input-group-btn>.btn+.btn{
+    margin-left:-1px
+}
+.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{
+    z-index:2
+}
+.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{
+    margin-right:-1px
+}
+.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{
+    z-index:2;
+    margin-left:-1px
+}
+.nav{
+    padding-left:0;
+    margin-bottom:0;
+    list-style:none
+}
+.nav>li{
+    position:relative;
+    display:block
+}
+.nav>li>a{
+    position:relative;
+    display:block;
+    padding:10px 15px
+}
+.nav>li>a:focus,.nav>li>a:hover{
+    text-decoration:none;
+    background-color:#eee
+}
+.nav>li.disabled>a{
+    color:#777
+}
+.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{
+    color:#777;
+    text-decoration:none;
+    cursor:not-allowed;
+    background-color:transparent
+}
+.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{
+    background-color:#eee;
+    border-color:#337ab7
+}
+.nav .nav-divider{
+    height:1px;
+    margin:9px 0;
+    overflow:hidden;
+    background-color:#e5e5e5
+}
+.nav>li>a>img{
+    max-width:none
+}
+.nav-tabs{
+    border-bottom:1px solid #ddd
+}
+.nav-tabs>li{
+    float:left;
+    margin-bottom:-1px
+}
+.nav-tabs>li>a{
+    margin-right:2px;
+    line-height:1.42857143;
+    border:1px solid transparent;
+    border-radius:4px 4px 0 0
+}
+.nav-tabs>li>a:hover{
+    border-color:#eee #eee #ddd
+}
+.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{
+    color:#555;
+    cursor:default;
+    background-color:#fff;
+    border:1px solid #ddd;
+    border-bottom-color:transparent
+}
+.nav-tabs.nav-justified{
+    width:100%;
+    border-bottom:0
+}
+.nav-tabs.nav-justified>li{
+    float:none
+}
+.nav-tabs.nav-justified>li>a{
+    margin-bottom:5px;
+    text-align:center
+}
+.nav-tabs.nav-justified>.dropdown .dropdown-menu{
+    top:auto;
+    left:auto
+}
+@media (min-width:768px){
+    .nav-tabs.nav-justified>li{
+        display:table-cell;
+        width:1%
+    }
+    .nav-tabs.nav-justified>li>a{
+        margin-bottom:0
+    }
+}
+.nav-tabs.nav-justified>li>a{
+    margin-right:0;
+    border-radius:4px
+}
+.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{
+    border:1px solid #ddd
+}
+@media (min-width:768px){
+    .nav-tabs.nav-justified>li>a{
+        border-bottom:1px solid #ddd;
+        border-radius:4px 4px 0 0
+    }
+    .nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{
+        border-bottom-color:#fff
+    }
+}
+.nav-pills>li{
+    float:left
+}
+.nav-pills>li>a{
+    border-radius:4px
+}
+.nav-pills>li+li{
+    margin-left:2px
+}
+.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{
+    color:#fff;
+    background-color:#337ab7
+}
+.nav-stacked>li{
+    float:none
+}
+.nav-stacked>li+li{
+    margin-top:2px;
+    margin-left:0
+}
+.nav-justified{
+    width:100%
+}
+.nav-justified>li{
+    float:none
+}
+.nav-justified>li>a{
+    margin-bottom:5px;
+    text-align:center
+}
+.nav-justified>.dropdown .dropdown-menu{
+    top:auto;
+    left:auto
+}
+@media (min-width:768px){
+    .nav-justified>li{
+        display:table-cell;
+        width:1%
+    }
+    .nav-justified>li>a{
+        margin-bottom:0
+    }
+}
+.nav-tabs-justified{
+    border-bottom:0
+}
+.nav-tabs-justified>li>a{
+    margin-right:0;
+    border-radius:4px
+}
+.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{
+    border:1px solid #ddd
+}
+@media (min-width:768px){
+    .nav-tabs-justified>li>a{
+        border-bottom:1px solid #ddd;
+        border-radius:4px 4px 0 0
+    }
+    .nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{
+        border-bottom-color:#fff
+    }
+}
+.tab-content>.tab-pane{
+    display:none
+}
+.tab-content>.active{
+    display:block
+}
+.nav-tabs .dropdown-menu{
+    margin-top:-1px;
+    border-top-left-radius:0;
+    border-top-right-radius:0
+}
+.navbar{
+    position:relative;
+    min-height:50px;
+    margin-bottom:20px;
+    border:1px solid transparent
+}
+@media (min-width:768px){
+    .navbar{
+        border-radius:4px
+    }
+}
+@media (min-width:768px){
+    .navbar-header{
+        float:left
+    }
+}
+.navbar-collapse{
+    padding-right:15px;
+    padding-left:15px;
+    overflow-x:visible;
+    -webkit-overflow-scrolling:touch;
+    border-top:1px solid transparent;
+    -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);
+    box-shadow:inset 0 1px 0 rgba(255,255,255,.1)
+}
+.navbar-collapse.in{
+    overflow-y:auto
+}
+@media (min-width:768px){
+    .navbar-collapse{
+        width:auto;
+        border-top:0;
+        -webkit-box-shadow:none;
+        box-shadow:none
+    }
+    .navbar-collapse.collapse{
+        display:block!important;
+        height:auto!important;
+        padding-bottom:0;
+        overflow:visible!important
+    }
+    .navbar-collapse.in{
+        overflow-y:visible
+    }
+    .navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{
+        padding-right:0;
+        padding-left:0
+    }
+}
+.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{
+    max-height:340px
+}
+@media (max-device-width:480px) and (orientation:landscape){
+    .navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{
+        max-height:200px
+    }
+}
+.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{
+    margin-right:-15px;
+    margin-left:-15px
+}
+@media (min-width:768px){
+    .container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{
+        margin-right:0;
+        margin-left:0
+    }
+}
+.navbar-static-top{
+    z-index:1000;
+    border-width:0 0 1px
+}
+@media (min-width:768px){
+    .navbar-static-top{
+        border-radius:0
+    }
+}
+.navbar-fixed-bottom,.navbar-fixed-top{
+    position:fixed;
+    right:0;
+    left:0;
+    z-index:1030
+}
+@media (min-width:768px){
+    .navbar-fixed-bottom,.navbar-fixed-top{
+        border-radius:0
+    }
+}
+.navbar-fixed-top{
+    top:0;
+    border-width:0 0 1px
+}
+.navbar-fixed-bottom{
+    bottom:0;
+    margin-bottom:0;
+    border-width:1px 0 0
+}
+.navbar-brand{
+    float:left;
+    height:50px;
+    padding:15px 15px;
+    font-size:18px;
+    line-height:20px
+}
+.navbar-brand:focus,.navbar-brand:hover{
+    text-decoration:none
+}
+.navbar-brand>img{
+    display:block
+}
+@media (min-width:768px){
+    .navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{
+        margin-left:-15px
+    }
+}
+.navbar-toggle{
+    position:relative;
+    float:right;
+    padding:9px 10px;
+    margin-top:8px;
+    margin-right:15px;
+    margin-bottom:8px;
+    background-color:transparent;
+    background-image:none;
+    border:1px solid transparent;
+    border-radius:4px
+}
+.navbar-toggle:focus{
+    outline:0
+}
+.navbar-toggle .icon-bar{
+    display:block;
+    width:22px;
+    height:2px;
+    border-radius:1px
+}
+.navbar-toggle .icon-bar+.icon-bar{
+    margin-top:4px
+}
+@media (min-width:768px){
+    .navbar-toggle{
+        display:none
+    }
+}
+.navbar-nav{
+    margin:7.5px -15px
+}
+.navbar-nav>li>a{
+    padding-top:10px;
+    padding-bottom:10px;
+    line-height:20px
+}
+@media (max-width:767px){
+    .navbar-nav .open .dropdown-menu{
+        position:static;
+        float:none;
+        width:auto;
+        margin-top:0;
+        background-color:transparent;
+        border:0;
+        -webkit-box-shadow:none;
+        box-shadow:none
+    }
+    .navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{
+        padding:5px 15px 5px 25px
+    }
+    .navbar-nav .open .dropdown-menu>li>a{
+        line-height:20px
+    }
+    .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{
+        background-image:none
+    }
+}
+@media (min-width:768px){
+    .navbar-nav{
+        float:left;
+        margin:0
+    }
+    .navbar-nav>li{
+        float:left
+    }
+    .navbar-nav>li>a{
+        padding-top:15px;
+        padding-bottom:15px
+    }
+}
+.navbar-form{
+    padding:10px 15px;
+    margin-top:8px;
+    margin-right:-15px;
+    margin-bottom:8px;
+    margin-left:-15px;
+    border-top:1px solid transparent;
+    border-bottom:1px solid transparent;
+    -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);
+    box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)
+}
+@media (min-width:768px){
+    .navbar-form .form-group{
+        display:inline-block;
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .navbar-form .form-control{
+        display:inline-block;
+        width:auto;
+        vertical-align:middle
+    }
+    .navbar-form .form-control-static{
+        display:inline-block
+    }
+    .navbar-form .input-group{
+        display:inline-table;
+        vertical-align:middle
+    }
+    .navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{
+        width:auto
+    }
+    .navbar-form .input-group>.form-control{
+        width:100%
+    }
+    .navbar-form .control-label{
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .navbar-form .checkbox,.navbar-form .radio{
+        display:inline-block;
+        margin-top:0;
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .navbar-form .checkbox label,.navbar-form .radio label{
+        padding-left:0
+    }
+    .navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{
+        position:relative;
+        margin-left:0
+    }
+    .navbar-form .has-feedback .form-control-feedback{
+        top:0
+    }
+}
+@media (max-width:767px){
+    .navbar-form .form-group{
+        margin-bottom:5px
+    }
+    .navbar-form .form-group:last-child{
+        margin-bottom:0
+    }
+}
+@media (min-width:768px){
+    .navbar-form{
+        width:auto;
+        padding-top:0;
+        padding-bottom:0;
+        margin-right:0;
+        margin-left:0;
+        border:0;
+        -webkit-box-shadow:none;
+        box-shadow:none
+    }
+}
+.navbar-nav>li>.dropdown-menu{
+    margin-top:0;
+    border-top-left-radius:0;
+    border-top-right-radius:0
+}
+.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{
+    margin-bottom:0;
+    border-top-left-radius:4px;
+    border-top-right-radius:4px;
+    border-bottom-right-radius:0;
+    border-bottom-left-radius:0
+}
+.navbar-btn{
+    margin-top:8px;
+    margin-bottom:8px
+}
+.navbar-btn.btn-sm{
+    margin-top:10px;
+    margin-bottom:10px
+}
+.navbar-btn.btn-xs{
+    margin-top:14px;
+    margin-bottom:14px
+}
+.navbar-text{
+    margin-top:15px;
+    margin-bottom:15px
+}
+@media (min-width:768px){
+    .navbar-text{
+        float:left;
+        margin-right:15px;
+        margin-left:15px
+    }
+}
+@media (min-width:768px){
+    .navbar-left{
+        float:left!important
+    }
+    .navbar-right{
+        float:right!important;
+        margin-right:-15px
+    }
+    .navbar-right~.navbar-right{
+        margin-right:0
+    }
+}
+.navbar-default{
+    background-color:#f8f8f8;
+    border-color:#e7e7e7
+}
+.navbar-default .navbar-brand{
+    color:#777
+}
+.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{
+    color:#5e5e5e;
+    background-color:transparent
+}
+.navbar-default .navbar-text{
+    color:#777
+}
+.navbar-default .navbar-nav>li>a{
+    color:#777
+}
+.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{
+    color:#333;
+    background-color:transparent
+}
+.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{
+    color:#555;
+    background-color:#e7e7e7
+}
+.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{
+    color:#ccc;
+    background-color:transparent
+}
+.navbar-default .navbar-toggle{
+    border-color:#ddd
+}
+.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{
+    background-color:#ddd
+}
+.navbar-default .navbar-toggle .icon-bar{
+    background-color:#888
+}
+.navbar-default .navbar-collapse,.navbar-default .navbar-form{
+    border-color:#e7e7e7
+}
+.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{
+    color:#555;
+    background-color:#e7e7e7
+}
+@media (max-width:767px){
+    .navbar-default .navbar-nav .open .dropdown-menu>li>a{
+        color:#777
+    }
+    .navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{
+        color:#333;
+        background-color:transparent
+    }
+    .navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{
+        color:#555;
+        background-color:#e7e7e7
+    }
+    .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{
+        color:#ccc;
+        background-color:transparent
+    }
+}
+.navbar-default .navbar-link{
+    color:#777
+}
+.navbar-default .navbar-link:hover{
+    color:#333
+}
+.navbar-default .btn-link{
+    color:#777
+}
+.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{
+    color:#333
+}
+.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{
+    color:#ccc
+}
+.navbar-inverse{
+    background-color:#222;
+    border-color:#080808
+}
+.navbar-inverse .navbar-brand{
+    color:#9d9d9d
+}
+.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{
+    color:#fff;
+    background-color:transparent
+}
+.navbar-inverse .navbar-text{
+    color:#9d9d9d
+}
+.navbar-inverse .navbar-nav>li>a{
+    color:#9d9d9d
+}
+.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{
+    color:#fff;
+    background-color:transparent
+}
+.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{
+    color:#fff;
+    background-color:#080808
+}
+.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{
+    color:#444;
+    background-color:transparent
+}
+.navbar-inverse .navbar-toggle{
+    border-color:#333
+}
+.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{
+    background-color:#333
+}
+.navbar-inverse .navbar-toggle .icon-bar{
+    background-color:#fff
+}
+.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{
+    border-color:#101010
+}
+.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{
+    color:#fff;
+    background-color:#080808
+}
+@media (max-width:767px){
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{
+        border-color:#080808
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu .divider{
+        background-color:#080808
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>li>a{
+        color:#9d9d9d
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{
+        color:#fff;
+        background-color:transparent
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{
+        color:#fff;
+        background-color:#080808
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{
+        color:#444;
+        background-color:transparent
+    }
+}
+.navbar-inverse .navbar-link{
+    color:#9d9d9d
+}
+.navbar-inverse .navbar-link:hover{
+    color:#fff
+}
+.navbar-inverse .btn-link{
+    color:#9d9d9d
+}
+.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{
+    color:#fff
+}
+.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{
+    color:#444
+}
+.breadcrumb{
+    padding:8px 15px;
+    margin-bottom:20px;
+    list-style:none;
+    background-color:#f5f5f5;
+    border-radius:4px
+}
+.breadcrumb>li{
+    display:inline-block
+}
+.breadcrumb>li+li:before{
+    padding:0 5px;
+    color:#ccc;
+    content:"/\00a0"
+}
+.breadcrumb>.active{
+    color:#777
+}
+.pagination{
+    display:inline-block;
+    padding-left:0;
+    margin:20px 0;
+    border-radius:4px
+}
+.pagination>li{
+    display:inline
+}
+.pagination>li>a,.pagination>li>span{
+    position:relative;
+    float:left;
+    padding:6px 12px;
+    margin-left:-1px;
+    line-height:1.42857143;
+    color:#337ab7;
+    text-decoration:none;
+    background-color:#fff;
+    border:1px solid #ddd
+}
+.pagination>li:first-child>a,.pagination>li:first-child>span{
+    margin-left:0;
+    border-top-left-radius:4px;
+    border-bottom-left-radius:4px
+}
+.pagination>li:last-child>a,.pagination>li:last-child>span{
+    border-top-right-radius:4px;
+    border-bottom-right-radius:4px
+}
+.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{
+    z-index:2;
+    color:#23527c;
+    background-color:#eee;
+    border-color:#ddd
+}
+.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{
+    z-index:3;
+    color:#fff;
+    cursor:default;
+    background-color:#337ab7;
+    border-color:#337ab7
+}
+.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{
+    color:#777;
+    cursor:not-allowed;
+    background-color:#fff;
+    border-color:#ddd
+}
+.pagination-lg>li>a,.pagination-lg>li>span{
+    padding:10px 16px;
+    font-size:18px;
+    line-height:1.3333333
+}
+.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{
+    border-top-left-radius:6px;
+    border-bottom-left-radius:6px
+}
+.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{
+    border-top-right-radius:6px;
+    border-bottom-right-radius:6px
+}
+.pagination-sm>li>a,.pagination-sm>li>span{
+    padding:5px 10px;
+    font-size:12px;
+    line-height:1.5
+}
+.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{
+    border-top-left-radius:3px;
+    border-bottom-left-radius:3px
+}
+.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{
+    border-top-right-radius:3px;
+    border-bottom-right-radius:3px
+}
+.pager{
+    padding-left:0;
+    margin:20px 0;
+    text-align:center;
+    list-style:none
+}
+.pager li{
+    display:inline
+}
+.pager li>a,.pager li>span{
+    display:inline-block;
+    padding:5px 14px;
+    background-color:#fff;
+    border:1px solid #ddd;
+    border-radius:15px
+}
+.pager li>a:focus,.pager li>a:hover{
+    text-decoration:none;
+    background-color:#eee
+}
+.pager .next>a,.pager .next>span{
+    float:right
+}
+.pager .previous>a,.pager .previous>span{
+    float:left
+}
+.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{
+    color:#777;
+    cursor:not-allowed;
+    background-color:#fff
+}
+.label{
+    display:inline;
+    padding:.2em .6em .3em;
+    font-size:75%;
+    font-weight:700;
+    line-height:1;
+    color:#fff;
+    text-align:center;
+    white-space:nowrap;
+    vertical-align:baseline;
+    border-radius:.25em
+}
+a.label:focus,a.label:hover{
+    color:#fff;
+    text-decoration:none;
+    cursor:pointer
+}
+.label:empty{
+    display:none
+}
+.btn .label{
+    position:relative;
+    top:-1px
+}
+.label-default{
+    background-color:#777
+}
+.label-default[href]:focus,.label-default[href]:hover{
+    background-color:#5e5e5e
+}
+.label-primary{
+    background-color:#337ab7
+}
+.label-primary[href]:focus,.label-primary[href]:hover{
+    background-color:#286090
+}
+.label-success{
+    background-color:#5cb85c
+}
+.label-success[href]:focus,.label-success[href]:hover{
+    background-color:#449d44
+}
+.label-info{
+    background-color:#5bc0de
+}
+.label-info[href]:focus,.label-info[href]:hover{
+    background-color:#31b0d5
+}
+.label-warning{
+    background-color:#f0ad4e
+}
+.label-warning[href]:focus,.label-warning[href]:hover{
+    background-color:#ec971f
+}
+.label-danger{
+    background-color:#d9534f
+}
+.label-danger[href]:focus,.label-danger[href]:hover{
+    background-color:#c9302c
+}
+.badge{
+    display:inline-block;
+    min-width:10px;
+    padding:3px 7px;
+    font-size:12px;
+    font-weight:700;
+    line-height:1;
+    color:#fff;
+    text-align:center;
+    white-space:nowrap;
+    vertical-align:middle;
+    background-color:#777;
+    border-radius:10px
+}
+.badge:empty{
+    display:none
+}
+.btn .badge{
+    position:relative;
+    top:-1px
+}
+.btn-group-xs>.btn .badge,.btn-xs .badge{
+    top:0;
+    padding:1px 5px
+}
+a.badge:focus,a.badge:hover{
+    color:#fff;
+    text-decoration:none;
+    cursor:pointer
+}
+.list-group-item.active>.badge,.nav-pills>.active>a>.badge{
+    color:#337ab7;
+    background-color:#fff
+}
+.list-group-item>.badge{
+    float:right
+}
+.list-group-item>.badge+.badge{
+    margin-right:5px
+}
+.nav-pills>li>a>.badge{
+    margin-left:3px
+}
+.jumbotron{
+    padding-top:30px;
+    padding-bottom:30px;
+    margin-bottom:30px;
+    color:inherit;
+    background-color:#eee
+}
+.jumbotron .h1,.jumbotron h1{
+    color:inherit
+}
+.jumbotron p{
+    margin-bottom:15px;
+    font-size:21px;
+    font-weight:200
+}
+.jumbotron>hr{
+    border-top-color:#d5d5d5
+}
+.container .jumbotron,.container-fluid .jumbotron{
+    padding-right:15px;
+    padding-left:15px;
+    border-radius:6px
+}
+.jumbotron .container{
+    max-width:100%
+}
+@media screen and (min-width:768px){
+    .jumbotron{
+        padding-top:48px;
+        padding-bottom:48px
+    }
+    .container .jumbotron,.container-fluid .jumbotron{
+        padding-right:60px;
+        padding-left:60px
+    }
+    .jumbotron .h1,.jumbotron h1{
+        font-size:63px
+    }
+}
+.thumbnail{
+    display:block;
+    padding:4px;
+    margin-bottom:20px;
+    line-height:1.42857143;
+    background-color:#fff;
+    border:1px solid #ddd;
+    border-radius:4px;
+    -webkit-transition:border .2s ease-in-out;
+    -o-transition:border .2s ease-in-out;
+    transition:border .2s ease-in-out
+}
+.thumbnail a>img,.thumbnail>img{
+    margin-right:auto;
+    margin-left:auto
+}
+a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{
+    border-color:#337ab7
+}
+.thumbnail .caption{
+    padding:9px;
+    color:#333
+}
+.alert{
+    padding:15px;
+    margin-bottom:20px;
+    border:1px solid transparent;
+    border-radius:4px
+}
+.alert h4{
+    margin-top:0;
+    color:inherit
+}
+.alert .alert-link{
+    font-weight:700
+}
+.alert>p,.alert>ul{
+    margin-bottom:0
+}
+.alert>p+p{
+    margin-top:5px
+}
+.alert-dismissable,.alert-dismissible{
+    padding-right:35px
+}
+.alert-dismissable .close,.alert-dismissible .close{
+    position:relative;
+    top:-2px;
+    right:-21px;
+    color:inherit
+}
+.alert-success{
+    color:#3c763d;
+    background-color:#dff0d8;
+    border-color:#d6e9c6
+}
+.alert-success hr{
+    border-top-color:#c9e2b3
+}
+.alert-success .alert-link{
+    color:#2b542c
+}
+.alert-info{
+    color:#31708f;
+    background-color:#d9edf7;
+    border-color:#bce8f1
+}
+.alert-info hr{
+    border-top-color:#a6e1ec
+}
+.alert-info .alert-link{
+    color:#245269
+}
+.alert-warning{
+    color:#8a6d3b;
+    background-color:#fcf8e3;
+    border-color:#faebcc
+}
+.alert-warning hr{
+    border-top-color:#f7e1b5
+}
+.alert-warning .alert-link{
+    color:#66512c
+}
+.alert-danger{
+    color:#a94442;
+    background-color:#f2dede;
+    border-color:#ebccd1
+}
+.alert-danger hr{
+    border-top-color:#e4b9c0
+}
+.alert-danger .alert-link{
+    color:#843534
+}
+@-webkit-keyframes progress-bar-stripes{
+    from{
+        background-position:40px 0
+    }
+    to{
+        background-position:0 0
+    }
+}
+@-o-keyframes progress-bar-stripes{
+    from{
+        background-position:40px 0
+    }
+    to{
+        background-position:0 0
+    }
+}
+@keyframes progress-bar-stripes{
+    from{
+        background-position:40px 0
+    }
+    to{
+        background-position:0 0
+    }
+}
+.progress{
+    height:20px;
+    margin-bottom:20px;
+    overflow:hidden;
+    background-color:#f5f5f5;
+    border-radius:4px;
+    -webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);
+    box-shadow:inset 0 1px 2px rgba(0,0,0,.1)
+}
+.progress-bar{
+    float:left;
+    width:0;
+    height:100%;
+    font-size:12px;
+    line-height:20px;
+    color:#fff;
+    text-align:center;
+    background-color:#337ab7;
+    -webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);
+    box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);
+    -webkit-transition:width .6s ease;
+    -o-transition:width .6s ease;
+    transition:width .6s ease
+}
+.progress-bar-striped,.progress-striped .progress-bar{
+    background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    -webkit-background-size:40px 40px;
+    background-size:40px 40px
+}
+.progress-bar.active,.progress.active .progress-bar{
+    -webkit-animation:progress-bar-stripes 2s linear infinite;
+    -o-animation:progress-bar-stripes 2s linear infinite;
+    animation:progress-bar-stripes 2s linear infinite
+}
+.progress-bar-success{
+    background-color:#5cb85c
+}
+.progress-striped .progress-bar-success{
+    background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+.progress-bar-info{
+    background-color:#5bc0de
+}
+.progress-striped .progress-bar-info{
+    background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+.progress-bar-warning{
+    background-color:#f0ad4e
+}
+.progress-striped .progress-bar-warning{
+    background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+.progress-bar-danger{
+    background-color:#d9534f
+}
+.progress-striped .progress-bar-danger{
+    background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+.media{
+    margin-top:15px
+}
+.media:first-child{
+    margin-top:0
+}
+.media,.media-body{
+    overflow:hidden;
+    zoom:1
+}
+.media-body{
+    width:10000px
+}
+.media-object{
+    display:block
+}
+.media-object.img-thumbnail{
+    max-width:none
+}
+.media-right,.media>.pull-right{
+    padding-left:10px
+}
+.media-left,.media>.pull-left{
+    padding-right:10px
+}
+.media-body,.media-left,.media-right{
+    display:table-cell;
+    vertical-align:top
+}
+.media-middle{
+    vertical-align:middle
+}
+.media-bottom{
+    vertical-align:bottom
+}
+.media-heading{
+    margin-top:0;
+    margin-bottom:5px
+}
+.media-list{
+    padding-left:0;
+    list-style:none
+}
+.list-group{
+    padding-left:0;
+    margin-bottom:20px
+}
+.list-group-item{
+    position:relative;
+    display:block;
+    padding:10px 15px;
+    margin-bottom:-1px;
+    background-color:#fff;
+    border:1px solid #ddd
+}
+.list-group-item:first-child{
+    border-top-left-radius:4px;
+    border-top-right-radius:4px
+}
+.list-group-item:last-child{
+    margin-bottom:0;
+    border-bottom-right-radius:4px;
+    border-bottom-left-radius:4px
+}
+a.list-group-item,button.list-group-item{
+    color:#555
+}
+a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{
+    color:#333
+}
+a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{
+    color:#555;
+    text-decoration:none;
+    background-color:#f5f5f5
+}
+button.list-group-item{
+    width:100%;
+    text-align:left
+}
+.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{
+    color:#777;
+    cursor:not-allowed;
+    background-color:#eee
+}
+.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{
+    color:inherit
+}
+.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{
+    color:#777
+}
+.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{
+    z-index:2;
+    color:#fff;
+    background-color:#337ab7;
+    border-color:#337ab7
+}
+.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{
+    color:inherit
+}
+.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{
+    color:#c7ddef
+}
+.list-group-item-success{
+    color:#3c763d;
+    background-color:#dff0d8
+}
+a.list-group-item-success,button.list-group-item-success{
+    color:#3c763d
+}
+a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{
+    color:inherit
+}
+a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{
+    color:#3c763d;
+    background-color:#d0e9c6
+}
+a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{
+    color:#fff;
+    background-color:#3c763d;
+    border-color:#3c763d
+}
+.list-group-item-info{
+    color:#31708f;
+    background-color:#d9edf7
+}
+a.list-group-item-info,button.list-group-item-info{
+    color:#31708f
+}
+a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{
+    color:inherit
+}
+a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{
+    color:#31708f;
+    background-color:#c4e3f3
+}
+a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{
+    color:#fff;
+    background-color:#31708f;
+    border-color:#31708f
+}
+.list-group-item-warning{
+    color:#8a6d3b;
+    background-color:#fcf8e3
+}
+a.list-group-item-warning,button.list-group-item-warning{
+    color:#8a6d3b
+}
+a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{
+    color:inherit
+}
+a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{
+    color:#8a6d3b;
+    background-color:#faf2cc
+}
+a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{
+    color:#fff;
+    background-color:#8a6d3b;
+    border-color:#8a6d3b
+}
+.list-group-item-danger{
+    color:#a94442;
+    background-color:#f2dede
+}
+a.list-group-item-danger,button.list-group-item-danger{
+    color:#a94442
+}
+a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{
+    color:inherit
+}
+a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{
+    color:#a94442;
+    background-color:#ebcccc
+}
+a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{
+    color:#fff;
+    background-color:#a94442;
+    border-color:#a94442
+}
+.list-group-item-heading{
+    margin-top:0;
+    margin-bottom:5px
+}
+.list-group-item-text{
+    margin-bottom:0;
+    line-height:1.3
+}
+.panel{
+    margin-bottom:20px;
+    background-color:#fff;
+    border:1px solid transparent;
+    border-radius:4px;
+    -webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);
+    box-shadow:0 1px 1px rgba(0,0,0,.05)
+}
+.panel-body{
+    padding:15px
+}
+.panel-heading{
+    padding:10px 15px;
+    border-bottom:1px solid transparent;
+    border-top-left-radius:3px;
+    border-top-right-radius:3px
+}
+.panel-heading>.dropdown .dropdown-toggle{
+    color:inherit
+}
+.panel-title{
+    margin-top:0;
+    margin-bottom:0;
+    font-size:16px;
+    color:inherit
+}
+.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{
+    color:inherit
+}
+.panel-footer{
+    padding:10px 15px;
+    background-color:#f5f5f5;
+    border-top:1px solid #ddd;
+    border-bottom-right-radius:3px;
+    border-bottom-left-radius:3px
+}
+.panel>.list-group,.panel>.panel-collapse>.list-group{
+    margin-bottom:0
+}
+.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{
+    border-width:1px 0;
+    border-radius:0
+}
+.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{
+    border-top:0;
+    border-top-left-radius:3px;
+    border-top-right-radius:3px
+}
+.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{
+    border-bottom:0;
+    border-bottom-right-radius:3px;
+    border-bottom-left-radius:3px
+}
+.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{
+    border-top-left-radius:0;
+    border-top-right-radius:0
+}
+.panel-heading+.list-group .list-group-item:first-child{
+    border-top-width:0
+}
+.list-group+.panel-footer{
+    border-top-width:0
+}
+.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{
+    margin-bottom:0
+}
+.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{
+    padding-right:15px;
+    padding-left:15px
+}
+.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{
+    border-top-left-radius:3px;
+    border-top-right-radius:3px
+}
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{
+    border-top-left-radius:3px;
+    border-top-right-radius:3px
+}
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{
+    border-top-left-radius:3px
+}
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{
+    border-top-right-radius:3px
+}
+.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{
+    border-bottom-right-radius:3px;
+    border-bottom-left-radius:3px
+}
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{
+    border-bottom-right-radius:3px;
+    border-bottom-left-radius:3px
+}
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{
+    border-bottom-left-radius:3px
+}
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{
+    border-bottom-right-radius:3px
+}
+.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{
+    border-top:1px solid #ddd
+}
+.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{
+    border-top:0
+}
+.panel>.table-bordered,.panel>.table-responsive>.table-bordered{
+    border:0
+}
+.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{
+    border-left:0
+}
+.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{
+    border-right:0
+}
+.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{
+    border-bottom:0
+}
+.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{
+    border-bottom:0
+}
+.panel>.table-responsive{
+    margin-bottom:0;
+    border:0
+}
+.panel-group{
+    margin-bottom:20px
+}
+.panel-group .panel{
+    margin-bottom:0;
+    border-radius:4px
+}
+.panel-group .panel+.panel{
+    margin-top:5px
+}
+.panel-group .panel-heading{
+    border-bottom:0
+}
+.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{
+    border-top:1px solid #ddd
+}
+.panel-group .panel-footer{
+    border-top:0
+}
+.panel-group .panel-footer+.panel-collapse .panel-body{
+    border-bottom:1px solid #ddd
+}
+.panel-default{
+    border-color:#ddd
+}
+.panel-default>.panel-heading{
+    color:#333;
+    background-color:#f5f5f5;
+    border-color:#ddd
+}
+.panel-default>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#ddd
+}
+.panel-default>.panel-heading .badge{
+    color:#f5f5f5;
+    background-color:#333
+}
+.panel-default>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#ddd
+}
+.panel-primary{
+    border-color:#337ab7
+}
+.panel-primary>.panel-heading{
+    color:#fff;
+    background-color:#337ab7;
+    border-color:#337ab7
+}
+.panel-primary>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#337ab7
+}
+.panel-primary>.panel-heading .badge{
+    color:#337ab7;
+    background-color:#fff
+}
+.panel-primary>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#337ab7
+}
+.panel-success{
+    border-color:#d6e9c6
+}
+.panel-success>.panel-heading{
+    color:#3c763d;
+    background-color:#dff0d8;
+    border-color:#d6e9c6
+}
+.panel-success>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#d6e9c6
+}
+.panel-success>.panel-heading .badge{
+    color:#dff0d8;
+    background-color:#3c763d
+}
+.panel-success>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#d6e9c6
+}
+.panel-info{
+    border-color:#bce8f1
+}
+.panel-info>.panel-heading{
+    color:#31708f;
+    background-color:#d9edf7;
+    border-color:#bce8f1
+}
+.panel-info>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#bce8f1
+}
+.panel-info>.panel-heading .badge{
+    color:#d9edf7;
+    background-color:#31708f
+}
+.panel-info>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#bce8f1
+}
+.panel-warning{
+    border-color:#faebcc
+}
+.panel-warning>.panel-heading{
+    color:#8a6d3b;
+    background-color:#fcf8e3;
+    border-color:#faebcc
+}
+.panel-warning>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#faebcc
+}
+.panel-warning>.panel-heading .badge{
+    color:#fcf8e3;
+    background-color:#8a6d3b
+}
+.panel-warning>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#faebcc
+}
+.panel-danger{
+    border-color:#ebccd1
+}
+.panel-danger>.panel-heading{
+    color:#a94442;
+    background-color:#f2dede;
+    border-color:#ebccd1
+}
+.panel-danger>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#ebccd1
+}
+.panel-danger>.panel-heading .badge{
+    color:#f2dede;
+    background-color:#a94442
+}
+.panel-danger>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#ebccd1
+}
+.embed-responsive{
+    position:relative;
+    display:block;
+    height:0;
+    padding:0;
+    overflow:hidden
+}
+.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{
+    position:absolute;
+    top:0;
+    bottom:0;
+    left:0;
+    width:100%;
+    height:100%;
+    border:0
+}
+.embed-responsive-16by9{
+    padding-bottom:56.25%
+}
+.embed-responsive-4by3{
+    padding-bottom:75%
+}
+.well{
+    min-height:20px;
+    padding:19px;
+    margin-bottom:20px;
+    background-color:#f5f5f5;
+    border:1px solid #e3e3e3;
+    border-radius:4px;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.05)
+}
+.well blockquote{
+    border-color:#ddd;
+    border-color:rgba(0,0,0,.15)
+}
+.well-lg{
+    padding:24px;
+    border-radius:6px
+}
+.well-sm{
+    padding:9px;
+    border-radius:3px
+}
+.close{
+    float:right;
+    font-size:21px;
+    font-weight:700;
+    line-height:1;
+    color:#000;
+    text-shadow:0 1px 0 #fff;
+    filter:alpha(opacity=20);
+    opacity:.2
+}
+.close:focus,.close:hover{
+    color:#000;
+    text-decoration:none;
+    cursor:pointer;
+    filter:alpha(opacity=50);
+    opacity:.5
+}
+button.close{
+    -webkit-appearance:none;
+    padding:0;
+    cursor:pointer;
+    background:0 0;
+    border:0
+}
+.modal-open{
+    overflow:hidden
+}
+.modal{
+    position:fixed;
+    top:0;
+    right:0;
+    bottom:0;
+    left:0;
+    z-index:1050;
+    display:none;
+    overflow:hidden;
+    -webkit-overflow-scrolling:touch;
+    outline:0
+}
+.modal.fade .modal-dialog{
+    -webkit-transition:-webkit-transform .3s ease-out;
+    -o-transition:-o-transform .3s ease-out;
+    transition:transform .3s ease-out;
+    -webkit-transform:translate(0,-25%);
+    -ms-transform:translate(0,-25%);
+    -o-transform:translate(0,-25%);
+    transform:translate(0,-25%)
+}
+.modal.in .modal-dialog{
+    -webkit-transform:translate(0,0);
+    -ms-transform:translate(0,0);
+    -o-transform:translate(0,0);
+    transform:translate(0,0)
+}
+.modal-open .modal{
+    overflow-x:hidden;
+    overflow-y:auto
+}
+.modal-dialog{
+    position:relative;
+    width:auto;
+    margin:10px
+}
+.modal-content{
+    position:relative;
+    background-color:#fff;
+    -webkit-background-clip:padding-box;
+    background-clip:padding-box;
+    border:1px solid #999;
+    border:1px solid rgba(0,0,0,.2);
+    border-radius:6px;
+    outline:0;
+    -webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);
+    box-shadow:0 3px 9px rgba(0,0,0,.5)
+}
+.modal-backdrop{
+    position:fixed;
+    top:0;
+    right:0;
+    bottom:0;
+    left:0;
+    z-index:1040;
+    background-color:#000
+}
+.modal-backdrop.fade{
+    filter:alpha(opacity=0);
+    opacity:0
+}
+.modal-backdrop.in{
+    filter:alpha(opacity=50);
+    opacity:.5
+}
+.modal-header{
+    padding:15px;
+    border-bottom:1px solid #e5e5e5
+}
+.modal-header .close{
+    margin-top:-2px
+}
+.modal-title{
+    margin:0;
+    line-height:1.42857143
+}
+.modal-body{
+    position:relative;
+    padding:15px
+}
+.modal-footer{
+    padding:15px;
+    text-align:right;
+    border-top:1px solid #e5e5e5
+}
+.modal-footer .btn+.btn{
+    margin-bottom:0;
+    margin-left:5px
+}
+.modal-footer .btn-group .btn+.btn{
+    margin-left:-1px
+}
+.modal-footer .btn-block+.btn-block{
+    margin-left:0
+}
+.modal-scrollbar-measure{
+    position:absolute;
+    top:-9999px;
+    width:50px;
+    height:50px;
+    overflow:scroll
+}
+@media (min-width:768px){
+    .modal-dialog{
+        width:600px;
+        margin:30px auto
+    }
+    .modal-content{
+        -webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);
+        box-shadow:0 5px 15px rgba(0,0,0,.5)
+    }
+    .modal-sm{
+        width:300px
+    }
+}
+@media (min-width:992px){
+    .modal-lg{
+        width:900px
+    }
+}
+.tooltip{
+    position:absolute;
+    z-index:1070;
+    display:block;
+    font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
+    font-size:12px;
+    font-style:normal;
+    font-weight:400;
+    line-height:1.42857143;
+    text-align:left;
+    text-align:start;
+    text-decoration:none;
+    text-shadow:none;
+    text-transform:none;
+    letter-spacing:normal;
+    word-break:normal;
+    word-spacing:normal;
+    word-wrap:normal;
+    white-space:normal;
+    filter:alpha(opacity=0);
+    opacity:0;
+    line-break:auto
+}
+.tooltip.in{
+    filter:alpha(opacity=90);
+    opacity:.9
+}
+.tooltip.top{
+    padding:5px 0;
+    margin-top:-3px
+}
+.tooltip.right{
+    padding:0 5px;
+    margin-left:3px
+}
+.tooltip.bottom{
+    padding:5px 0;
+    margin-top:3px
+}
+.tooltip.left{
+    padding:0 5px;
+    margin-left:-3px
+}
+.tooltip-inner{
+    max-width:200px;
+    padding:3px 8px;
+    color:#fff;
+    text-align:center;
+    background-color:#000;
+    border-radius:4px
+}
+.tooltip-arrow{
+    position:absolute;
+    width:0;
+    height:0;
+    border-color:transparent;
+    border-style:solid
+}
+.tooltip.top .tooltip-arrow{
+    bottom:0;
+    left:50%;
+    margin-left:-5px;
+    border-width:5px 5px 0;
+    border-top-color:#000
+}
+.tooltip.top-left .tooltip-arrow{
+    right:5px;
+    bottom:0;
+    margin-bottom:-5px;
+    border-width:5px 5px 0;
+    border-top-color:#000
+}
+.tooltip.top-right .tooltip-arrow{
+    bottom:0;
+    left:5px;
+    margin-bottom:-5px;
+    border-width:5px 5px 0;
+    border-top-color:#000
+}
+.tooltip.right .tooltip-arrow{
+    top:50%;
+    left:0;
+    margin-top:-5px;
+    border-width:5px 5px 5px 0;
+    border-right-color:#000
+}
+.tooltip.left .tooltip-arrow{
+    top:50%;
+    right:0;
+    margin-top:-5px;
+    border-width:5px 0 5px 5px;
+    border-left-color:#000
+}
+.tooltip.bottom .tooltip-arrow{
+    top:0;
+    left:50%;
+    margin-left:-5px;
+    border-width:0 5px 5px;
+    border-bottom-color:#000
+}
+.tooltip.bottom-left .tooltip-arrow{
+    top:0;
+    right:5px;
+    margin-top:-5px;
+    border-width:0 5px 5px;
+    border-bottom-color:#000
+}
+.tooltip.bottom-right .tooltip-arrow{
+    top:0;
+    left:5px;
+    margin-top:-5px;
+    border-width:0 5px 5px;
+    border-bottom-color:#000
+}
+.popover{
+    position:absolute;
+    top:0;
+    left:0;
+    z-index:1060;
+    display:none;
+    max-width:276px;
+    padding:1px;
+    font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
+    font-size:14px;
+    font-style:normal;
+    font-weight:400;
+    line-height:1.42857143;
+    text-align:left;
+    text-align:start;
+    text-decoration:none;
+    text-shadow:none;
+    text-transform:none;
+    letter-spacing:normal;
+    word-break:normal;
+    word-spacing:normal;
+    word-wrap:normal;
+    white-space:normal;
+    background-color:#fff;
+    -webkit-background-clip:padding-box;
+    background-clip:padding-box;
+    border:1px solid #ccc;
+    border:1px solid rgba(0,0,0,.2);
+    border-radius:6px;
+    -webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);
+    box-shadow:0 5px 10px rgba(0,0,0,.2);
+    line-break:auto
+}
+.popover.top{
+    margin-top:-10px
+}
+.popover.right{
+    margin-left:10px
+}
+.popover.bottom{
+    margin-top:10px
+}
+.popover.left{
+    margin-left:-10px
+}
+.popover-title{
+    padding:8px 14px;
+    margin:0;
+    font-size:14px;
+    background-color:#f7f7f7;
+    border-bottom:1px solid #ebebeb;
+    border-radius:5px 5px 0 0
+}
+.popover-content{
+    padding:9px 14px
+}
+.popover>.arrow,.popover>.arrow:after{
+    position:absolute;
+    display:block;
+    width:0;
+    height:0;
+    border-color:transparent;
+    border-style:solid
+}
+.popover>.arrow{
+    border-width:11px
+}
+.popover>.arrow:after{
+    content:"";
+    border-width:10px
+}
+.popover.top>.arrow{
+    bottom:-11px;
+    left:50%;
+    margin-left:-11px;
+    border-top-color:#999;
+    border-top-color:rgba(0,0,0,.25);
+    border-bottom-width:0
+}
+.popover.top>.arrow:after{
+    bottom:1px;
+    margin-left:-10px;
+    content:" ";
+    border-top-color:#fff;
+    border-bottom-width:0
+}
+.popover.right>.arrow{
+    top:50%;
+    left:-11px;
+    margin-top:-11px;
+    border-right-color:#999;
+    border-right-color:rgba(0,0,0,.25);
+    border-left-width:0
+}
+.popover.right>.arrow:after{
+    bottom:-10px;
+    left:1px;
+    content:" ";
+    border-right-color:#fff;
+    border-left-width:0
+}
+.popover.bottom>.arrow{
+    top:-11px;
+    left:50%;
+    margin-left:-11px;
+    border-top-width:0;
+    border-bottom-color:#999;
+    border-bottom-color:rgba(0,0,0,.25)
+}
+.popover.bottom>.arrow:after{
+    top:1px;
+    margin-left:-10px;
+    content:" ";
+    border-top-width:0;
+    border-bottom-color:#fff
+}
+.popover.left>.arrow{
+    top:50%;
+    right:-11px;
+    margin-top:-11px;
+    border-right-width:0;
+    border-left-color:#999;
+    border-left-color:rgba(0,0,0,.25)
+}
+.popover.left>.arrow:after{
+    right:1px;
+    bottom:-10px;
+    content:" ";
+    border-right-width:0;
+    border-left-color:#fff
+}
+.carousel{
+    position:relative
+}
+.carousel-inner{
+    position:relative;
+    width:100%;
+    overflow:hidden
+}
+.carousel-inner>.item{
+    position:relative;
+    display:none;
+    -webkit-transition:.6s ease-in-out left;
+    -o-transition:.6s ease-in-out left;
+    transition:.6s ease-in-out left
+}
+.carousel-inner>.item>a>img,.carousel-inner>.item>img{
+    line-height:1
+}
+@media all and (transform-3d),(-webkit-transform-3d){
+    .carousel-inner>.item{
+        -webkit-transition:-webkit-transform .6s ease-in-out;
+        -o-transition:-o-transform .6s ease-in-out;
+        transition:transform .6s ease-in-out;
+        -webkit-backface-visibility:hidden;
+        backface-visibility:hidden;
+        -webkit-perspective:1000px;
+        perspective:1000px
+    }
+    .carousel-inner>.item.active.right,.carousel-inner>.item.next{
+        left:0;
+        -webkit-transform:translate3d(100%,0,0);
+        transform:translate3d(100%,0,0)
+    }
+    .carousel-inner>.item.active.left,.carousel-inner>.item.prev{
+        left:0;
+        -webkit-transform:translate3d(-100%,0,0);
+        transform:translate3d(-100%,0,0)
+    }
+    .carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{
+        left:0;
+        -webkit-transform:translate3d(0,0,0);
+        transform:translate3d(0,0,0)
+    }
+}
+.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{
+    display:block
+}
+.carousel-inner>.active{
+    left:0
+}
+.carousel-inner>.next,.carousel-inner>.prev{
+    position:absolute;
+    top:0;
+    width:100%
+}
+.carousel-inner>.next{
+    left:100%
+}
+.carousel-inner>.prev{
+    left:-100%
+}
+.carousel-inner>.next.left,.carousel-inner>.prev.right{
+    left:0
+}
+.carousel-inner>.active.left{
+    left:-100%
+}
+.carousel-inner>.active.right{
+    left:100%
+}
+.carousel-control{
+    position:absolute;
+    top:0;
+    bottom:0;
+    left:0;
+    width:15%;
+    font-size:20px;
+    color:#fff;
+    text-align:center;
+    text-shadow:0 1px 2px rgba(0,0,0,.6);
+    background-color:rgba(0,0,0,0);
+    filter:alpha(opacity=50);
+    opacity:.5
+}
+.carousel-control.left{
+    background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
+    background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
+    background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));
+    background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
+    filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
+    background-repeat:repeat-x
+}
+.carousel-control.right{
+    right:0;
+    left:auto;
+    background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
+    background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
+    background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));
+    background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
+    filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
+    background-repeat:repeat-x
+}
+.carousel-control:focus,.carousel-control:hover{
+    color:#fff;
+    text-decoration:none;
+    filter:alpha(opacity=90);
+    outline:0;
+    opacity:.9
+}
+.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{
+    position:absolute;
+    top:50%;
+    z-index:5;
+    display:inline-block;
+    margin-top:-10px
+}
+.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{
+    left:50%;
+    margin-left:-10px
+}
+.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{
+    right:50%;
+    margin-right:-10px
+}
+.carousel-control .icon-next,.carousel-control .icon-prev{
+    width:20px;
+    height:20px;
+    font-family:serif;
+    line-height:1
+}
+.carousel-control .icon-prev:before{
+    content:'\2039'
+}
+.carousel-control .icon-next:before{
+    content:'\203a'
+}
+.carousel-indicators{
+    position:absolute;
+    bottom:10px;
+    left:50%;
+    z-index:15;
+    width:60%;
+    padding-left:0;
+    margin-left:-30%;
+    text-align:center;
+    list-style:none
+}
+.carousel-indicators li{
+    display:inline-block;
+    width:10px;
+    height:10px;
+    margin:1px;
+    text-indent:-999px;
+    cursor:pointer;
+    background-color:#000\9;
+    background-color:rgba(0,0,0,0);
+    border:1px solid #fff;
+    border-radius:10px
+}
+.carousel-indicators .active{
+    width:12px;
+    height:12px;
+    margin:0;
+    background-color:#fff
+}
+.carousel-caption{
+    position:absolute;
+    right:15%;
+    bottom:20px;
+    left:15%;
+    z-index:10;
+    padding-top:20px;
+    padding-bottom:20px;
+    color:#fff;
+    text-align:center;
+    text-shadow:0 1px 2px rgba(0,0,0,.6)
+}
+.carousel-caption .btn{
+    text-shadow:none
+}
+@media screen and (min-width:768px){
+    .carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{
+        width:30px;
+        height:30px;
+        margin-top:-10px;
+        font-size:30px
+    }
+    .carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{
+        margin-left:-10px
+    }
+    .carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{
+        margin-right:-10px
+    }
+    .carousel-caption{
+        right:20%;
+        left:20%;
+        padding-bottom:30px
+    }
+    .carousel-indicators{
+        bottom:20px
+    }
+}
+.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{
+    display:table;
+    content:" "
+}
+.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{
+    clear:both
+}
+.center-block{
+    display:block;
+    margin-right:auto;
+    margin-left:auto
+}
+.pull-right{
+    float:right!important
+}
+.pull-left{
+    float:left!important
+}
+.hide{
+    display:none!important
+}
+.show{
+    display:block!important
+}
+.invisible{
+    visibility:hidden
+}
+.text-hide{
+    font:0/0 a;
+    color:transparent;
+    text-shadow:none;
+    background-color:transparent;
+    border:0
+}
+.hidden{
+    display:none!important
+}
+.affix{
+    position:fixed
+}
+@-ms-viewport{
+    width:device-width
+}
+.visible-lg,.visible-md,.visible-sm,.visible-xs{
+    display:none!important
+}
+.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{
+    display:none!important
+}
+@media (max-width:767px){
+    .visible-xs{
+        display:block!important
+    }
+    table.visible-xs{
+        display:table!important
+    }
+    tr.visible-xs{
+        display:table-row!important
+    }
+    td.visible-xs,th.visible-xs{
+        display:table-cell!important
+    }
+}
+@media (max-width:767px){
+    .visible-xs-block{
+        display:block!important
+    }
+}
+@media (max-width:767px){
+    .visible-xs-inline{
+        display:inline!important
+    }
+}
+@media (max-width:767px){
+    .visible-xs-inline-block{
+        display:inline-block!important
+    }
+}
+@media (min-width:768px) and (max-width:991px){
+    .visible-sm{
+        display:block!important
+    }
+    table.visible-sm{
+        display:table!important
+    }
+    tr.visible-sm{
+        display:table-row!important
+    }
+    td.visible-sm,th.visible-sm{
+        display:table-cell!important
+    }
+}
+@media (min-width:768px) and (max-width:991px){
+    .visible-sm-block{
+        display:block!important
+    }
+}
+@media (min-width:768px) and (max-width:991px){
+    .visible-sm-inline{
+        display:inline!important
+    }
+}
+@media (min-width:768px) and (max-width:991px){
+    .visible-sm-inline-block{
+        display:inline-block!important
+    }
+}
+@media (min-width:992px) and (max-width:1199px){
+    .visible-md{
+        display:block!important
+    }
+    table.visible-md{
+        display:table!important
+    }
+    tr.visible-md{
+        display:table-row!important
+    }
+    td.visible-md,th.visible-md{
+        display:table-cell!important
+    }
+}
+@media (min-width:992px) and (max-width:1199px){
+    .visible-md-block{
+        display:block!important
+    }
+}
+@media (min-width:992px) and (max-width:1199px){
+    .visible-md-inline{
+        display:inline!important
+    }
+}
+@media (min-width:992px) and (max-width:1199px){
+    .visible-md-inline-block{
+        display:inline-block!important
+    }
+}
+@media (min-width:1200px){
+    .visible-lg{
+        display:block!important
+    }
+    table.visible-lg{
+        display:table!important
+    }
+    tr.visible-lg{
+        display:table-row!important
+    }
+    td.visible-lg,th.visible-lg{
+        display:table-cell!important
+    }
+}
+@media (min-width:1200px){
+    .visible-lg-block{
+        display:block!important
+    }
+}
+@media (min-width:1200px){
+    .visible-lg-inline{
+        display:inline!important
+    }
+}
+@media (min-width:1200px){
+    .visible-lg-inline-block{
+        display:inline-block!important
+    }
+}
+@media (max-width:767px){
+    .hidden-xs{
+        display:none!important
+    }
+}
+@media (min-width:768px) and (max-width:991px){
+    .hidden-sm{
+        display:none!important
+    }
+}
+@media (min-width:992px) and (max-width:1199px){
+    .hidden-md{
+        display:none!important
+    }
+}
+@media (min-width:1200px){
+    .hidden-lg{
+        display:none!important
+    }
+}
+.visible-print{
+    display:none!important
+}
+@media print{
+    .visible-print{
+        display:block!important
+    }
+    table.visible-print{
+        display:table!important
+    }
+    tr.visible-print{
+        display:table-row!important
+    }
+    td.visible-print,th.visible-print{
+        display:table-cell!important
+    }
+}
+.visible-print-block{
+    display:none!important
+}
+@media print{
+    .visible-print-block{
+        display:block!important
+    }
+}
+.visible-print-inline{
+    display:none!important
+}
+@media print{
+    .visible-print-inline{
+        display:inline!important
+    }
+}
+.visible-print-inline-block{
+    display:none!important
+}
+@media print{
+    .visible-print-inline-block{
+        display:inline-block!important
+    }
+}
+@media print{
+    .hidden-print{
+        display:none!important
+    }
+}
+/*# sourceMappingURL=bootstrap.min.css.map */
+ 
diff --git a/themes/squares/client/src/css/01-main.css b/themes/squares/client/src/css/01-main.css
new file mode 100644
index 00000000..be80c222
--- /dev/null
+++ b/themes/squares/client/src/css/01-main.css
@@ -0,0 +1,77 @@
+body {
+  /*background-image: url("//*img//*LargeTriangles.svg");*/
+  /*background-image: url("//*img//*RandomizedPattern.svg");*/
+  background-image: url("/img/background.svg");
+  /*background-color:#000000;*/
+}
+canvas{
+  position:absolute;
+  top:0;
+  left:0;
+}
+.authelia-brand {
+  font-weight: bold;
+  font-style: italic;
+  color: #ffffff
+}
+.poweredby-block {
+  margin: 0px 30px;
+  margin-top: 10px;
+  padding-top: 15px;
+  border-top: 1px solid rgba(0, 0, 0, 0.15);
+
+}
+.poweredby {
+  font-size: 0.7em;
+  color: white;
+}
+/* notifications */
+.notification {
+  padding: 10px;
+  margin: 15px 0px;
+  border-radius: 6px;
+  display: none;
+  position: absolute;
+}
+.notification img {
+  width: 24px;
+  margin-right: 10px;
+}
+.notification i,
+.notification span {
+  display:table-cell;
+  vertical-align:middle;
+}
+.info {
+  border: 1px solid #9cb1ff;
+  background-color: rgb(192, 220, 255);
+}
+.success {
+  border: 1px solid #65ec7c;
+  background-color: rgb(163, 255, 157);
+}
+.error {
+  border: 1px solid #ffa3a3;
+  background-color: rgb(255, 175, 175);
+}
+.warning {
+  border: 1px solid #ffd743;
+  background-color: rgb(255, 230, 143);
+}
+.bottom-right-links {
+  text-align: right;
+  margin-top: 10px;
+  font-size: 0.8em;
+  color: white;
+}
+.header {
+  background-color: #000000;
+  color: white;
+  margin: 0px;
+}
+.body {
+  padding: 10px;
+}
+h1 {
+  font-size: 25px;
+}
diff --git a/themes/squares/client/src/css/02-login.css b/themes/squares/client/src/css/02-login.css
new file mode 100644
index 00000000..a6984267
--- /dev/null
+++ b/themes/squares/client/src/css/02-login.css
@@ -0,0 +1,136 @@
+.form-signin
+{
+    margin: 0 auto;
+}
+
+.form-signin .form-signin-heading, .form-signin .checkbox
+{
+    margin-bottom: 10px;
+}
+
+.form-signin .checkbox
+{
+    font-weight: normal;
+}
+
+.form-signin .form-control
+{
+    position: relative;
+    font-size: 16px;
+    height: auto;
+    padding: 10px;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+}
+.form-signin .form-control:focus
+{
+    z-index: 2;
+}
+.form-signin input[type="text"]
+{
+    margin-bottom: -1px;
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+}
+.form-signin input[type="password"]
+{
+    /* margin-bottom: 10px; */
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
+}
+.account-wall
+{
+    border: 1px solid #000;
+    margin-top: 20px;
+    padding-bottom: 20px;
+    background-color: #000000;
+    -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 1);
+    -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 1);
+    box-shadow: 0px 2px 2px rgba(0, 0, 0, 1);
+}
+.account-wall h1
+{
+    margin-bottom: 15px;
+    margin-top: 15px;
+    font-weight: 800;
+    display: block;
+    text-align: center;
+}
+.account-wall h3
+{
+    display: block;
+    text-align: center;
+}
+.account-wall p 
+{
+    text-align: center;
+    margin: 10px;
+    color: white;
+}
+.account-wall .form-inputs 
+{
+    margin-bottom: 10px;
+    border-color: #b20c0c;
+}
+.account-wall hr {
+    border-color: #c5c5c5;
+}
+
+.header-img
+{
+    width: 96px;
+    height: 96px;
+    margin: 0 auto 10px;
+    display: block;
+    -moz-border-radius: 50%;
+    -webkit-border-radius: 50%;
+    border-radius: 50%;
+}
+
+.link
+{
+    margin-top: 10px;
+    color: white;
+}
+
+.btn-primary.totp
+{
+    background-color: rgb(102, 135, 162);
+}
+
+.btn-primary.u2f
+{
+    background-color: rgb(83, 149, 204);
+}
+
+.u2f-token {
+    text-align: center;
+}
+
+.u2f-token img {
+    width: 70px;
+}
+
+.keep-me-logged-in {
+    margin-top: 10px;
+    font-size: 0.8em;
+    color: white;
+}
+
+.keep-me-logged-in input[type=checkbox] {
+    transform: scale(0.8);
+    margin: 0;
+    margin-right: 4px;
+}
+
+.keep-me-logged-in label {
+    font-weight: 300;
+}
+
+.keep-me-logged-in input,
+.keep-me-logged-in label {
+    display: inline-block;
+    margin-bottom: 0; /* I added this after I posted my reply */
+    vertical-align: middle; /* Fixes any weird issues in Firefox and IE */
+}
diff --git a/themes/squares/client/src/css/03-errors.css b/themes/squares/client/src/css/03-errors.css
new file mode 100644
index 00000000..e9f97f33
--- /dev/null
+++ b/themes/squares/client/src/css/03-errors.css
@@ -0,0 +1,12 @@
+
+.error-401 .header-img {
+    border-radius: 0%;
+}
+
+.error-403 .header-img {
+    border-radius: 0%;
+}
+
+.error-404 .header-img {
+    border-radius: 0%;
+}
\ No newline at end of file
diff --git a/themes/squares/client/src/css/03-password-reset-form.css b/themes/squares/client/src/css/03-password-reset-form.css
new file mode 100644
index 00000000..34066bc2
--- /dev/null
+++ b/themes/squares/client/src/css/03-password-reset-form.css
@@ -0,0 +1,4 @@
+
+.password-reset-form .header-img {
+    border-radius: 0%;
+}
diff --git a/themes/squares/client/src/css/03-password-reset-request.css b/themes/squares/client/src/css/03-password-reset-request.css
new file mode 100644
index 00000000..1a2ad4df
--- /dev/null
+++ b/themes/squares/client/src/css/03-password-reset-request.css
@@ -0,0 +1,4 @@
+
+.password-reset-request .header-img {
+    border-radius: 0%;
+}
diff --git a/themes/squares/client/src/css/03-totp-register.css b/themes/squares/client/src/css/03-totp-register.css
new file mode 100644
index 00000000..cb76720a
--- /dev/null
+++ b/themes/squares/client/src/css/03-totp-register.css
@@ -0,0 +1,22 @@
+.totp-register #secret {
+  background-color: white;
+  font-size: 0.9em;
+  font-weight: bold;
+  padding: 5px;
+  border: 1px solid #c7c7c7;
+  word-wrap: break-word;
+}
+.totp-register #qrcode img {
+  margin: 10px auto;
+}
+.totp-register .need-google-authenticator {
+  text-align: center;
+  margin-top: 20px;
+}
+.totp-register .store-badges {
+  margin-top: 5px;
+}
+.totp-register .store-badge {
+  width: 110px;
+  height: 30px;
+}
\ No newline at end of file
diff --git a/themes/squares/client/src/css/03-u2f-register.css b/themes/squares/client/src/css/03-u2f-register.css
new file mode 100644
index 00000000..e54cddf8
--- /dev/null
+++ b/themes/squares/client/src/css/03-u2f-register.css
@@ -0,0 +1,5 @@
+
+.u2f-register img {
+    display: block;
+    margin: 20px auto;
+}
\ No newline at end of file
diff --git a/themes/squares/client/src/img/LargeTriangles.svg b/themes/squares/client/src/img/LargeTriangles.svg
new file mode 100644
index 00000000..0988bcb3
--- /dev/null
+++ b/themes/squares/client/src/img/LargeTriangles.svg
@@ -0,0 +1 @@
+<svg xmlns='http://www.w3.org/2000/svg' width='300' height='250' viewBox='0 0 1080 900'><rect fill='#000000' width='1080' height='900'/><g fill-opacity='0.16'><polygon fill='#444' points='90 150 0 300 180 300'/><polygon points='90 150 180 0 0 0'/><polygon fill='#AAA' points='270 150 360 0 180 0'/><polygon fill='#DDD' points='450 150 360 300 540 300'/><polygon fill='#999' points='450 150 540 0 360 0'/><polygon points='630 150 540 300 720 300'/><polygon fill='#DDD' points='630 150 720 0 540 0'/><polygon fill='#444' points='810 150 720 300 900 300'/><polygon fill='#FFF' points='810 150 900 0 720 0'/><polygon fill='#DDD' points='990 150 900 300 1080 300'/><polygon fill='#444' points='990 150 1080 0 900 0'/><polygon fill='#DDD' points='90 450 0 600 180 600'/><polygon points='90 450 180 300 0 300'/><polygon fill='#666' points='270 450 180 600 360 600'/><polygon fill='#AAA' points='270 450 360 300 180 300'/><polygon fill='#DDD' points='450 450 360 600 540 600'/><polygon fill='#999' points='450 450 540 300 360 300'/><polygon fill='#999' points='630 450 540 600 720 600'/><polygon fill='#FFF' points='630 450 720 300 540 300'/><polygon points='810 450 720 600 900 600'/><polygon fill='#DDD' points='810 450 900 300 720 300'/><polygon fill='#AAA' points='990 450 900 600 1080 600'/><polygon fill='#444' points='990 450 1080 300 900 300'/><polygon fill='#222' points='90 750 0 900 180 900'/><polygon points='270 750 180 900 360 900'/><polygon fill='#DDD' points='270 750 360 600 180 600'/><polygon points='450 750 540 600 360 600'/><polygon points='630 750 540 900 720 900'/><polygon fill='#444' points='630 750 720 600 540 600'/><polygon fill='#AAA' points='810 750 720 900 900 900'/><polygon fill='#666' points='810 750 900 600 720 600'/><polygon fill='#999' points='990 750 900 900 1080 900'/><polygon fill='#999' points='180 0 90 150 270 150'/><polygon fill='#444' points='360 0 270 150 450 150'/><polygon fill='#FFF' points='540 0 450 150 630 150'/><polygon points='900 0 810 150 990 150'/><polygon fill='#222' points='0 300 -90 450 90 450'/><polygon fill='#FFF' points='0 300 90 150 -90 150'/><polygon fill='#FFF' points='180 300 90 450 270 450'/><polygon fill='#666' points='180 300 270 150 90 150'/><polygon fill='#222' points='360 300 270 450 450 450'/><polygon fill='#FFF' points='360 300 450 150 270 150'/><polygon fill='#444' points='540 300 450 450 630 450'/><polygon fill='#222' points='540 300 630 150 450 150'/><polygon fill='#AAA' points='720 300 630 450 810 450'/><polygon fill='#666' points='720 300 810 150 630 150'/><polygon fill='#FFF' points='900 300 810 450 990 450'/><polygon fill='#999' points='900 300 990 150 810 150'/><polygon points='0 600 -90 750 90 750'/><polygon fill='#666' points='0 600 90 450 -90 450'/><polygon fill='#AAA' points='180 600 90 750 270 750'/><polygon fill='#444' points='180 600 270 450 90 450'/><polygon fill='#444' points='360 600 270 750 450 750'/><polygon fill='#999' points='360 600 450 450 270 450'/><polygon fill='#666' points='540 600 630 450 450 450'/><polygon fill='#222' points='720 600 630 750 810 750'/><polygon fill='#FFF' points='900 600 810 750 990 750'/><polygon fill='#222' points='900 600 990 450 810 450'/><polygon fill='#DDD' points='0 900 90 750 -90 750'/><polygon fill='#444' points='180 900 270 750 90 750'/><polygon fill='#FFF' points='360 900 450 750 270 750'/><polygon fill='#AAA' points='540 900 630 750 450 750'/><polygon fill='#FFF' points='720 900 810 750 630 750'/><polygon fill='#222' points='900 900 990 750 810 750'/><polygon fill='#222' points='1080 300 990 450 1170 450'/><polygon fill='#FFF' points='1080 300 1170 150 990 150'/><polygon points='1080 600 990 750 1170 750'/><polygon fill='#666' points='1080 600 1170 450 990 450'/><polygon fill='#DDD' points='1080 900 1170 750 990 750'/></g></svg>
\ No newline at end of file
diff --git a/themes/squares/client/src/img/RandomizedPattern.svg b/themes/squares/client/src/img/RandomizedPattern.svg
new file mode 100644
index 00000000..51afee6d
--- /dev/null
+++ b/themes/squares/client/src/img/RandomizedPattern.svg
@@ -0,0 +1 @@
+<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='100' height='50' viewBox='0 0 100 50'><rect fill='#000000' width='50' height='25'/><defs><rect stroke='#000000' stroke-width='0.5' width='1' height='1' id='s'/><pattern id='a' width='2' height='2' patternUnits='userSpaceOnUse'><g stroke='#000000' stroke-width='0.5'><rect fill='#050505' width='1' height='1'/><rect fill='#000000' width='1' height='1' x='1' y='1'/><rect fill='#0a0a0a' width='1' height='1' y='1'/><rect fill='#0f0f0f' width='1' height='1' x='1'/></g></pattern><pattern id='b' width='5' height='11' patternUnits='userSpaceOnUse'><g fill='#141414'><use xlink:href='#s' x='2' y='0'/><use xlink:href='#s' x='4' y='1'/><use xlink:href='#s' x='1' y='2'/><use xlink:href='#s' x='2' y='4'/><use xlink:href='#s' x='4' y='6'/><use xlink:href='#s' x='0' y='8'/><use xlink:href='#s' x='3' y='9'/></g></pattern><pattern id='c' width='7' height='7' patternUnits='userSpaceOnUse'><g fill='#1a1a1a'><use xlink:href='#s' x='1' y='1'/><use xlink:href='#s' x='3' y='4'/><use xlink:href='#s' x='5' y='6'/><use xlink:href='#s' x='0' y='3'/></g></pattern><pattern id='d' width='11' height='5' patternUnits='userSpaceOnUse'><g fill='#000000'><use xlink:href='#s' x='1' y='1'/><use xlink:href='#s' x='6' y='3'/><use xlink:href='#s' x='8' y='2'/><use xlink:href='#s' x='3' y='0'/><use xlink:href='#s' x='0' y='3'/></g><g fill='#1f1f1f'><use xlink:href='#s' x='8' y='3'/><use xlink:href='#s' x='4' y='2'/><use xlink:href='#s' x='5' y='4'/><use xlink:href='#s' x='10' y='0'/></g></pattern><pattern id='e' width='47' height='23' patternUnits='userSpaceOnUse'><g fill='#000000'><use xlink:href='#s' x='2' y='5'/><use xlink:href='#s' x='23' y='13'/><use xlink:href='#s' x='4' y='18'/><use xlink:href='#s' x='35' y='9'/></g></pattern><pattern id='f' width='61' height='31' patternUnits='userSpaceOnUse'><g fill='#000000'><use xlink:href='#s' x='16' y='0'/><use xlink:href='#s' x='13' y='22'/><use xlink:href='#s' x='44' y='15'/><use xlink:href='#s' x='12' y='11'/></g></pattern></defs><rect fill='url(#a)' width='100' height='50'/><rect fill='url(#b)' width='100' height='50'/><rect fill='url(#c)' width='100' height='50'/><rect fill='url(#d)' width='100' height='50'/><rect fill='url(#e)' width='100' height='50'/><rect fill='url(#f)' width='100' height='50'/></svg>
\ No newline at end of file
diff --git a/themes/squares/client/src/img/background.jpg b/themes/squares/client/src/img/background.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..974ea273fa87adccec4b4433fc4d097c04ae4c31
GIT binary patch
literal 587
zcmb7<L2d#u3`M__34}>za3BG;m3jxdn?<FO;0Ua`=skLeO1)6UA)wuGli(4e5-l+5
zk2kTNzrXV?AHeH&)q;qyk%#<^XK1Cm5*1R$8dDUe91aK8m)^V5xvHLURfpO+o^hDQ
zl#(CM7qhsS#1uOd(lS$+kujrKxhno!`4hq76;GN1R3IHFZ;>P_E@h=1|8$YR;59+9
zW`I5}XRiI4&WC@yzle_&cQR_(0pU8Ji5j{Gs2||wia~Q)aTB2CVR=3aT5jO?)niY+
P^@QfW&?n$dCIR+GQO8ew

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/background.svg b/themes/squares/client/src/img/background.svg
new file mode 100644
index 00000000..668312f9
--- /dev/null
+++ b/themes/squares/client/src/img/background.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="56" height="100">
+<rect width="56" height="100" fill="#000000"></rect>
+<path d="M28 66L0 50L0 16L28 0L56 16L56 50L28 66L28 100" fill="none" stroke="#FCFCFC" stroke-width="2"></path>
+<path d="M28 0L28 34L0 50L0 84L28 100L56 84L56 50L28 34" fill="none" stroke="#FBFBFB" stroke-width="2"></path>
+</svg>
diff --git a/themes/squares/client/src/img/icon.png b/themes/squares/client/src/img/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..040d10c1ab5feaf6720fa8e8c0a0aa336402894e
GIT binary patch
literal 1461
zcmV;m1xosfP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800006VoOIv0000h
z08pkdLy7<Z010qNS#tmYE+YT{E+YYWr9XB6000McNliru;s_oG8!(+tmiGVv02y>e
zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00kXML_t(o!_`+?Y*bYgUHhE-
zn3=ZI7qzh{%0mzcVu-|44KcBlMg%4JNF)*ye@ry-m;Ooo@C(p{@FNm6K%#tzfV8Dp
z(gHC?u?eAyEkY@6v9v&$K4#|5J!kuI=XL2!XD|}uPVUd$_w2p)Ui)z_@E;5PlS6Y?
zzD$W&;<c#bG`q|<OHK2`(Z1F*_Z`5B=eE$u=<U^0j`K;>aqBgCCWIgr3<ka|x8+B}
z=a2UPs{j_R*hn!>%#B17TU=LfaFniBfPfGHk@o-q0}>pSg3elo_&jXcox?pxuirC(
z+64_w<1wcpt~Ct9s1v|4#r%X~rnEyMkPy>M!hxWQY!F}#Xodt_W_^yC&$ChylYpwS
z&=(^YTQB4VP`#+(neoZ!X+^?|Fo&2@Mx>OVR#uekyFNbs3W#>4vjG6i{zq#-D?lmO
zgwb%&er)to`<;|MBis1xiRpQoq=1lspman6NDy#26twqNR#fgA_~q+c*8$*>20$i(
zX43)A%7y?m0U*TPM9r<qXcd6m05r&RoHm4m;gEgAmh$xI#g^d-02A2<SOZKs0Fakk
zCV;f}N;04Ylv2Ko8hJ~C0f4tf`c7QSTS2Kbrw{nRCq)1PUM6U!-pFG)X}mxQr@rxh
z%NZxx&ItDaAiIbP3TFUA=k9FE-|@ywV|M{5Mwq>NfXHy+Yt0<CGX!NMV4F_bX_3Cu
z6leytj)3)gF#nvPg|ic(!y#Mc1rUoTx(oSxaOq1;waYiG^KtBDU<#}^ps%F`%pZG!
z@ZDuK7P-_uke7mR$W{}vgl0hG;;m*$)0<=C;|CNG#6!=2pfhQn0(c{?w0B`))3ady
z%0Cl4FjCNznvH_Um5u=-jHPHOD-ABr*1N8|IHB3gHn0o87637TH>3Y#2NpCw4d56E
zr8CNk8ARxwwFZ)MCdHT&Pdu5eC<zAkNFjQ&&OQagySVy88|F8z0_Zr1DnP>DZpn@^
zg!ri-09%OTsSF5HzMiccym0J>)_N^~%UQkOi}{T!LDU98Rq07el%z1z(&3<exFCS?
zva(J?c=IzJPpo=))%qG=(T|)R0OJKP_a}$WG7#DzXpR@T$gvX`0V#(p+v+a}psJ?)
z&!BCd02o@sm>7?5ok7xHr~3h{1!EAv9uU=n1bkaDm3plKtq}^CpAB|@>l6g=Th{?L
zO><|;%(Uw^S3mw@#S9R`U6(-E0l-S{0N(|L(*w#PM1vt?cfmoFzLeC?=_s+x9)RJv
zx_T-a+gbFrCOycqq7T~N44pf2vuFUnckNe}Et`c$GGYn$Em5~-bAIoXqv}29Cef4~
z2yM@YUF1_#SCxNnnX)rE+6@p6Qr(*6e&3X1gC-+a8c+&BL+%*qK6K@t0rdTNNQVOU
z`=%5Qz|c$<11C_oX0Z=|Z|P*x4y9laU$!iJR{F$j%3FW5_5>uoM=4&3>Oj<9vSgh%
zpy=+qV9L1*!8#Ez<Qro>-`$zbhb**wobmo&McKSC1NF&>%U#7=0}m|ebs1q0HD!v9
zy$h(TT*3Ty{Ax#AA^WnJjZ3BTP6{EanRzux5Qt1y^Q$1#gAm97P#~o>v3^Gnb?z_5
zc2NL$a)2xAmbJ20*BIQu3@cgwp7UhUS%QW_rq~p}dg}0f`!}4kbp4`eG`2hHsz#kW
zZxFdbQ|=801KV!&9J_Phe@-7=xryR#eBI=9yqTE?O9J-xk&7+8_&>_uokeL63{>@u
P00000NkvXXu0mjfXm6c7

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/mail.png b/themes/squares/client/src/img/mail.png
new file mode 100644
index 0000000000000000000000000000000000000000..834bfce9107a94be10da4c011fb78e37a192888f
GIT binary patch
literal 3545
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
Jtx8RN)PI?rK$`#n

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/matrix_circle_128x128.png b/themes/squares/client/src/img/matrix_circle_128x128.png
new file mode 100644
index 0000000000000000000000000000000000000000..856e01556b91e0ac25959bd41e9af51888d9ac56
GIT binary patch
literal 35750
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

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/notifications/.directory b/themes/squares/client/src/img/notifications/.directory
new file mode 100644
index 00000000..7c8b8054
--- /dev/null
+++ b/themes/squares/client/src/img/notifications/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,57,35
+Version=3
+ViewMode=1
diff --git a/themes/squares/client/src/img/notifications/error.png b/themes/squares/client/src/img/notifications/error.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf64d28f7519c816cf348e8a1d39ef0a5d588cf1
GIT binary patch
literal 863
zcmV-l1EBngP)<h;3K|Lk000e1NJLTq001BW001Be0{{R3M5Kzw00003b3#c}2nYz<
z;ZNWI000SaNLh0L007_s007_tqF?^X0000PbVXQnQ*UN;cVTj60C#tHE@^ISb7Ns}
zWiD@WXPfRk8UO$RHBd}cMgRZ*|4B*zU_kU$O7&7l;666mJT>%BO3pDW&@U?SP)W=!
zDc?Ui-aR+*QAzPrN%B%k(lRg6F)Yt8EY2<}@KQ<fQc1=qBgQBs@lr|hQc2i4GsGn#
z(K0Q+As)RR9Pv^~@lr|gQc1QK6}A@^x*Hk08yT_^60;K$tqu;Y4-c&n5Uvjot`HEf
z6BDo(7qJ~3vKt%6ZEeSHZqqa`(|&%`r>E7hu-4Yr*R{3Rwzk*2yw|<G*VotBy}j9x
zklEPS+LM#o*4EoRHrqWn+m@Ew*4EsZnBSwL;Av&xq@>}er{!*H?d|RE?(XmJ@9|Pe
z@l#6iRZa2L)$&_X^xod}eRuVKcle{8_^zq=uB!R9vH9)o`n|dOzPkFrz5Mt0{o&yK
z=jQ(M@&5Su|N8m=|NkefO3nZP04a1*PE!B}2pAb5A}ud5Nl;l_gqN7AtiQp*$ji&x
z<L2q=>+SOM_51t${QmxXRol}500C!7L_t(I%XO1yLxV67h6AluQ2_@iwQ8v|?ok!@
z-YYn2>#nW&|35*<zyR_6!g23IayfDUL{c=<a$MK3Oicl4WsM?=R5Y>)Th&V_rjo9T
ze#S;A*_l5-mq!xibJ6!wKOoo|s*V1UP3zFvtN+N2dmiZIe60TG@1IwnMAb&U!p;(&
z)}V2vi81~RiCPdeuW8l@`p5cH$Duxa;$!@DOgp9kjcwZ?dHnuDpI=KyB(DLJ)l<bW
zoxxc`n84x{*F^tn4pPDb4w4X;flmnsxF}7W6Q2;S47l5!ft+w9Tiklyc0>%Oiwu0v
z+v3DX>X<hk(K-YAl8rjqq`=N11-cTgK7f!ChttF#SK>cwFHCNP3#`xu`i0+p@$j}v
zvupdO%QTv77cWeCo)rccc+eBSa%GrMK}}rR1Tof<y7n<*v_mpD0#Rs{jvgnbx?`g!
p3@;%j2HGU^&BlLdxVKXn|6j&?*~IE2*wO$1002ovPDHLkV1fhnr_}%e

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/notifications/info.png b/themes/squares/client/src/img/notifications/info.png
new file mode 100644
index 0000000000000000000000000000000000000000..67928e88c95fca77407947ff12e43039dd0b3067
GIT binary patch
literal 732
zcmV<20wev2P)<h;3K|Lk000e1NJLTq001BW001Be0{{R3M5Kzw00003b3#c}2nYz<
z;ZNWI000SaNLh0L0089x0089ykL8;@0000PbVXQnQ*UN;cVTj60C#tHE@^ISb7Ns}
zWiD@WXPfRk8UO$Q_E1bzMgRZ*B(~cizuzFW)hW2vD7Dfiw$dfI(<QdkCAZThxYH%L
z(j~UjCAZThx6>uJ(<QglCAZTix6>!M(<r#qEV<Mzy45ba)iAr&GP~9`z1BLu*E_z~
zJipjKzt}{<*+;_KNW<Do!`oBF+*QZjS;*d7$=+Sb-d)PxW6R%T%;06r;Azd_Z_na%
z(c^m4<bl=ZgVyGX*yoJd=#ks$mE7u>-Rha$>YCo`p5N@L;_j;B?zHCcxaje_>G8bj
z^1kZw$L#db@AlE~_S5k8)$#Y&^7q~J_~G^W<o5dT`uy|z{q_9)`u+a={{H>`|Nj5~
z|Ns9nkp&R|000qmQchC<2M8D$ElGrzn8C=}<MQ+M)u=IB0003yNkl<ZILn1oS6jkR
z3{6YvqJseLjT2FEBjO$?PR{@TGd>`_N%@rX)a0Z|ZgN7D!E%EzO4BF|T#Lrx_z7o7
zd?!cSUYQljo-O%8iN{bX7`_+_qS82+VrJ&JnEfw#z64H(U-54aTjihr0J@_p!#+G!
zy3)t@`pEEzh@4#i^|Mv;7Key@{s%shl_<~B(S5fjCT5W<>1t;HU|V=x3WPOt0|0}0
z;R+};#==wo_zLLBoFPTVXzf+G2XtgWM5*d=5kONq(p*3b_?p!LkqWp59$A+NrCECi
zE?74ypUGPQc2z*22i4BEz^)$kGw`7J#8S(ueFE+LvgEqg05q!2ySiGp5X?S+^Vhc>
zR)P_dI{|=~6(ejrC)y2Qa66arabZmMn}^G$K_mOghco%~CLizL=l=r{;7UBdz#9|*
O0000<MNUMnLSTYudwt~q

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/notifications/success.png b/themes/squares/client/src/img/notifications/success.png
new file mode 100644
index 0000000000000000000000000000000000000000..d3998392dec4edddb1b62c2c9b81f6bc8c3e4132
GIT binary patch
literal 931
zcmV;U16=%xP)<h;3K|Lk000e1NJLTq001BW001Be0{{R3M5Kzw00003b3#c}2nYz<
z;ZNWI000SaNLh0L007_s007_tqF?^X0000PbVXQnQ*UN;cVTj60C#tHE@^ISb7Ns}
zWiD@WXPfRk8UO$RZct2AMgRZ*B(`lpzkoT$aVN2M52a}}zkoBidMmScD7SbXvUfAP
zeJHbeF}ZpjtaKc#b27PnGrD^+x_mFVdmpWJ7^!m}t#uu(bTYbp8?1CPx_mOad@i?o
zGrD{ks&f;jaWcDn5v6c4x_mOad=sW|6Q*$yq;L?Va1o?%4We%jqi+wSZwjDp5~Xny
zrg0Ugau=y{8mn|As(vf5fH1jxF}Zv)u!b_ad^50yG_Z#>yM8sXh&8)@HnE5{yMH&b
zi8!)}IJ|&4vWh&ugFU{3KE8xOzlA}+hC{%IMZkzm!;DhJkyFKyRK=22#*$UWlUBZ)
zR>qT8#*|sVoM_0SZO)>6)2n{dtbo+6fz_^p&$W-)xRcttr{c%C=hM&c;Lz{k((vNc
z@Z;k4?d0|E<o537_wVZW^6U5W?D+KT`Sb1g^zHfc?fLZX`1S7j^zZuh@cQ-f`u6tx
z`S$$!`273){{8*_{r&#_|NsAug0YnV001m>QchC<2M7rb5f~XBAS*37Npg6EmzbTd
zzP`c1#K_v+<K*x1^Yiug`1<|*{{H@<nC93300D1FL_t(I%XO3YTf#69h65^~SZ(X7
zMHIn>#*M~}1NYv0PuyGb-`g~4NNM?g=#$?2rOjRL0O0aSsuqeyLz*gifKz@Yg191z
zKL>l|7_uoQdqwWkkwdyK%Y$L$qHvJ!cjbnIeZ#AxJkseoWH&ZpK4mk%m=iCbf1Qqz
zTu8Qxldn8tjL$}lm15K{;^3>#m}rMo%mX9`Uwbh*KI%1IB%rcG?JhFD&iHE|>={5R
z(9G!4E&e^A{EhKx2w`_Z10fQfzcWIaePgxxPWjU$B<idKQDQhYgPA?FW$`4WYNdul
z8vZ-^po{c4<1kKtF?E4OrAI9eo7f>|M90H0=dj*NGj+-Ny})4&FI4Kv;zI(5W!X~C
zUYq=9P~`C<;j-pFNvaznhqborUbw;E+cw@2us2MiAKF!!+j5C68*eOdG_B3iQsUC~
zf}^?Ab#S<&p*YXzQAc}KVcy|29BtJBm-*)7Ur{cM|38<V#k&v!+G79!002ovPDHLk
FV1l=5+6w>x

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/notifications/warning.png b/themes/squares/client/src/img/notifications/warning.png
new file mode 100644
index 0000000000000000000000000000000000000000..ab8b54ffc0ea43e214c5b0a9ca13a8b590ded293
GIT binary patch
literal 580
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy%*9TgAsieWw;%dH0CG7CJR*yM
z^m`CyyfQK10+1nD;u=vBoS#-wo>-L1P+nfHmzkGcoSayYs+V7sKKq@G6j0H$0G|-o
z|NsBro}_YrzQU{d+OKD7zL>B3e6H?`c{<PM>%Ew#`y9xfr~3@Z1+ro2RJG>m8qL$S
znrCV?&(>?6YtTC1sCB+c>jDrpYh7&Cy4<F9wL|M_r}ni@?dx6IH@dZN^l0De)4tuW
zeP^Q1-AOw4rs&+Cs`GG~&co?Ck7npRo~iR>w(iq8y3c@UF36%l#cL8k7YddH`2{nu
zb4W<)dbUiTaqz^g$Dclby}H6Mnt_3l+0(@_#Nu>o@X4}620T-m3x$N|_$<_Zxi6UO
z#sB(s?ca`0x@1-??YV86;6(=US2wog3cr4k!4t14H$f}^j_b{hPT^vg?4v($bVq(W
zKm8B)w9gM~zwM7*D_4BvynO4A|3VhRk^5e2Y)<88Xg}kvq@@4Kp5cJsmq(o|Cbcpi
zSpGnCqPI~3>jRC7B9V~n<p-i9nXY!tU#&M|=du+)*luVzFLC=Q_hRb8g<&7FAGk?I
z2^X;2%wU-Nh_k{>vt(klsYhDhGT*w&)XQlP>IKiN(4Ay_`qsC<)~{1nmVR!PD}COZ
z(3kxnC7)%nN;&_XJ{5J>)4TcQI?JNIU2Zq7t-5^t_xGRmjOKwIbEe*~^8khwgQu&X
J%Q~loCIDF$0tNs8

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/padlock.png b/themes/squares/client/src/img/padlock.png
new file mode 100644
index 0000000000000000000000000000000000000000..31abbaeefcbc507ac59450a76b222d0f4e2ef298
GIT binary patch
literal 3265
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

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/password_white.png b/themes/squares/client/src/img/password_white.png
new file mode 100644
index 0000000000000000000000000000000000000000..0b93ef3fb7535313d9378066d0485f69e6e627df
GIT binary patch
literal 3858
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

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/pendrive.png b/themes/squares/client/src/img/pendrive.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa49178c326631167ec642aaacc117c38b31f6bf
GIT binary patch
literal 6721
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
Hcm4kWRQYbi

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/sharingan.png b/themes/squares/client/src/img/sharingan.png
new file mode 100644
index 0000000000000000000000000000000000000000..526787d3be6a78d936b6a21941291994d0eca38e
GIT binary patch
literal 9213
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
G0sjMgjHl56

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/stores/.directory b/themes/squares/client/src/img/stores/.directory
new file mode 100644
index 00000000..7bdc8daf
--- /dev/null
+++ b/themes/squares/client/src/img/stores/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,57,25
+Version=3
+ViewMode=1
diff --git a/themes/squares/client/src/img/stores/applestore-badge.svg b/themes/squares/client/src/img/stores/applestore-badge.svg
new file mode 100644
index 00000000..ac111e59
--- /dev/null
+++ b/themes/squares/client/src/img/stores/applestore-badge.svg
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="US_UK_Download_on_the" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+	 x="0px" y="0px" width="135px" height="40px" viewBox="0 0 135 40" enable-background="new 0 0 135 40" xml:space="preserve">
+<g>
+	<path fill="#A6A6A6" d="M130.197,40H4.729C2.122,40,0,37.872,0,35.267V4.726C0,2.12,2.122,0,4.729,0h125.468
+		C132.803,0,135,2.12,135,4.726v30.541C135,37.872,132.803,40,130.197,40L130.197,40z"/>
+	<path d="M134.032,35.268c0,2.116-1.714,3.83-3.834,3.83H4.729c-2.119,0-3.839-1.714-3.839-3.83V4.725
+		c0-2.115,1.72-3.835,3.839-3.835h125.468c2.121,0,3.834,1.72,3.834,3.835L134.032,35.268L134.032,35.268z"/>
+	<g>
+		<g>
+			<path fill="#FFFFFF" d="M30.128,19.784c-0.029-3.223,2.639-4.791,2.761-4.864c-1.511-2.203-3.853-2.504-4.676-2.528
+				c-1.967-0.207-3.875,1.177-4.877,1.177c-1.022,0-2.565-1.157-4.228-1.123c-2.14,0.033-4.142,1.272-5.24,3.196
+				c-2.266,3.923-0.576,9.688,1.595,12.859c1.086,1.553,2.355,3.287,4.016,3.226c1.625-0.067,2.232-1.036,4.193-1.036
+				c1.943,0,2.513,1.036,4.207,0.997c1.744-0.028,2.842-1.56,3.89-3.127c1.255-1.78,1.759-3.533,1.779-3.623
+				C33.507,24.924,30.161,23.647,30.128,19.784z"/>
+			<path fill="#FFFFFF" d="M26.928,10.306c0.874-1.093,1.472-2.58,1.306-4.089c-1.265,0.056-2.847,0.875-3.758,1.944
+				c-0.806,0.942-1.526,2.486-1.34,3.938C24.557,12.205,26.016,11.382,26.928,10.306z"/>
+		</g>
+	</g>
+	<g>
+		<path fill="#FFFFFF" d="M53.645,31.504h-2.271l-1.244-3.909h-4.324l-1.185,3.909h-2.211l4.284-13.308h2.646L53.645,31.504z
+			 M49.755,25.955L48.63,22.48c-0.119-0.355-0.342-1.191-0.671-2.507h-0.04c-0.131,0.566-0.342,1.402-0.632,2.507l-1.105,3.475
+			H49.755z"/>
+		<path fill="#FFFFFF" d="M64.662,26.588c0,1.632-0.441,2.922-1.323,3.869c-0.79,0.843-1.771,1.264-2.942,1.264
+			c-1.264,0-2.172-0.454-2.725-1.362h-0.04v5.055h-2.132V25.067c0-1.026-0.027-2.079-0.079-3.159h1.875l0.119,1.521h0.04
+			c0.711-1.146,1.79-1.718,3.238-1.718c1.132,0,2.077,0.447,2.833,1.342C64.284,23.949,64.662,25.127,64.662,26.588z M62.49,26.666
+			c0-0.934-0.21-1.704-0.632-2.31c-0.461-0.632-1.08-0.948-1.856-0.948c-0.526,0-1.004,0.176-1.431,0.523
+			c-0.428,0.35-0.708,0.807-0.839,1.373c-0.066,0.264-0.099,0.48-0.099,0.65v1.6c0,0.698,0.214,1.287,0.642,1.768
+			s0.984,0.721,1.668,0.721c0.803,0,1.428-0.31,1.875-0.928C62.266,28.496,62.49,27.68,62.49,26.666z"/>
+		<path fill="#FFFFFF" d="M75.699,26.588c0,1.632-0.441,2.922-1.324,3.869c-0.789,0.843-1.77,1.264-2.941,1.264
+			c-1.264,0-2.172-0.454-2.724-1.362H68.67v5.055h-2.132V25.067c0-1.026-0.027-2.079-0.079-3.159h1.875l0.119,1.521h0.04
+			c0.71-1.146,1.789-1.718,3.238-1.718c1.131,0,2.076,0.447,2.834,1.342C75.32,23.949,75.699,25.127,75.699,26.588z M73.527,26.666
+			c0-0.934-0.211-1.704-0.633-2.31c-0.461-0.632-1.078-0.948-1.855-0.948c-0.527,0-1.004,0.176-1.432,0.523
+			c-0.428,0.35-0.707,0.807-0.838,1.373c-0.065,0.264-0.099,0.48-0.099,0.65v1.6c0,0.698,0.214,1.287,0.64,1.768
+			c0.428,0.48,0.984,0.721,1.67,0.721c0.803,0,1.428-0.31,1.875-0.928C73.303,28.496,73.527,27.68,73.527,26.666z"/>
+		<path fill="#FFFFFF" d="M88.039,27.772c0,1.132-0.393,2.053-1.182,2.764c-0.867,0.777-2.074,1.165-3.625,1.165
+			c-1.432,0-2.58-0.276-3.449-0.829l0.494-1.777c0.936,0.566,1.963,0.85,3.082,0.85c0.803,0,1.428-0.182,1.877-0.544
+			c0.447-0.362,0.67-0.848,0.67-1.454c0-0.54-0.184-0.995-0.553-1.364c-0.367-0.369-0.98-0.712-1.836-1.029
+			c-2.33-0.869-3.494-2.142-3.494-3.816c0-1.094,0.408-1.991,1.225-2.689c0.814-0.699,1.9-1.048,3.258-1.048
+			c1.211,0,2.217,0.211,3.02,0.632l-0.533,1.738c-0.75-0.408-1.598-0.612-2.547-0.612c-0.75,0-1.336,0.185-1.756,0.553
+			c-0.355,0.329-0.533,0.73-0.533,1.205c0,0.526,0.203,0.961,0.611,1.303c0.355,0.316,1,0.658,1.936,1.027
+			c1.145,0.461,1.986,1,2.527,1.618C87.77,26.081,88.039,26.852,88.039,27.772z"/>
+		<path fill="#FFFFFF" d="M95.088,23.508h-2.35v4.659c0,1.185,0.414,1.777,1.244,1.777c0.381,0,0.697-0.033,0.947-0.099l0.059,1.619
+			c-0.42,0.157-0.973,0.236-1.658,0.236c-0.842,0-1.5-0.257-1.975-0.77c-0.473-0.514-0.711-1.376-0.711-2.587v-4.837h-1.4v-1.6h1.4
+			v-1.757l2.094-0.632v2.389h2.35V23.508z"/>
+		<path fill="#FFFFFF" d="M105.691,26.627c0,1.475-0.422,2.686-1.264,3.633c-0.883,0.975-2.055,1.461-3.516,1.461
+			c-1.408,0-2.529-0.467-3.365-1.401s-1.254-2.113-1.254-3.534c0-1.487,0.43-2.705,1.293-3.652c0.861-0.948,2.023-1.422,3.484-1.422
+			c1.408,0,2.541,0.467,3.396,1.402C105.283,24.021,105.691,25.192,105.691,26.627z M103.479,26.696
+			c0-0.885-0.189-1.644-0.572-2.277c-0.447-0.766-1.086-1.148-1.914-1.148c-0.857,0-1.508,0.383-1.955,1.148
+			c-0.383,0.634-0.572,1.405-0.572,2.317c0,0.885,0.189,1.644,0.572,2.276c0.461,0.766,1.105,1.148,1.936,1.148
+			c0.814,0,1.453-0.39,1.914-1.168C103.281,28.347,103.479,27.58,103.479,26.696z"/>
+		<path fill="#FFFFFF" d="M112.621,23.783c-0.211-0.039-0.436-0.059-0.672-0.059c-0.75,0-1.33,0.283-1.738,0.85
+			c-0.355,0.5-0.533,1.132-0.533,1.895v5.035h-2.131l0.02-6.574c0-1.106-0.027-2.113-0.08-3.021h1.857l0.078,1.836h0.059
+			c0.225-0.631,0.58-1.139,1.066-1.52c0.475-0.343,0.988-0.514,1.541-0.514c0.197,0,0.375,0.014,0.533,0.039V23.783z"/>
+		<path fill="#FFFFFF" d="M122.156,26.252c0,0.382-0.025,0.704-0.078,0.967h-6.396c0.025,0.948,0.334,1.673,0.928,2.173
+			c0.539,0.447,1.236,0.671,2.092,0.671c0.947,0,1.811-0.151,2.588-0.454l0.334,1.48c-0.908,0.396-1.98,0.593-3.217,0.593
+			c-1.488,0-2.656-0.438-3.506-1.313c-0.848-0.875-1.273-2.05-1.273-3.524c0-1.447,0.395-2.652,1.186-3.613
+			c0.828-1.026,1.947-1.539,3.355-1.539c1.383,0,2.43,0.513,3.141,1.539C121.873,24.047,122.156,25.055,122.156,26.252z
+			 M120.123,25.699c0.014-0.632-0.125-1.178-0.414-1.639c-0.369-0.593-0.936-0.889-1.699-0.889c-0.697,0-1.264,0.289-1.697,0.869
+			c-0.355,0.461-0.566,1.014-0.631,1.658H120.123z"/>
+	</g>
+	<g>
+		<g>
+			<path fill="#FFFFFF" d="M49.05,10.009c0,1.177-0.353,2.063-1.058,2.658c-0.653,0.549-1.581,0.824-2.783,0.824
+				c-0.596,0-1.106-0.026-1.533-0.078V6.982c0.557-0.09,1.157-0.136,1.805-0.136c1.145,0,2.008,0.249,2.59,0.747
+				C48.723,8.156,49.05,8.961,49.05,10.009z M47.945,10.038c0-0.763-0.202-1.348-0.606-1.756c-0.404-0.407-0.994-0.611-1.771-0.611
+				c-0.33,0-0.611,0.022-0.844,0.068v4.889c0.129,0.02,0.365,0.029,0.708,0.029c0.802,0,1.421-0.223,1.857-0.669
+				S47.945,10.892,47.945,10.038z"/>
+			<path fill="#FFFFFF" d="M54.909,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.009,0.718-1.727,0.718
+				c-0.692,0-1.243-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.712-0.698
+				c0.692,0,1.248,0.229,1.669,0.688C54.708,9.757,54.909,10.333,54.909,11.037z M53.822,11.071c0-0.435-0.094-0.808-0.281-1.119
+				c-0.22-0.376-0.533-0.564-0.94-0.564c-0.421,0-0.741,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
+				c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.714-0.191,0.94-0.574
+				C53.725,11.882,53.822,11.506,53.822,11.071z"/>
+			<path fill="#FFFFFF" d="M62.765,8.719l-1.475,4.714h-0.96l-0.611-2.047c-0.155-0.511-0.281-1.019-0.379-1.523h-0.019
+				c-0.091,0.518-0.217,1.025-0.379,1.523l-0.649,2.047h-0.971l-1.387-4.714h1.077l0.533,2.241c0.129,0.53,0.235,1.035,0.32,1.513
+				h0.019c0.078-0.394,0.207-0.896,0.389-1.503l0.669-2.25h0.854l0.641,2.202c0.155,0.537,0.281,1.054,0.378,1.552h0.029
+				c0.071-0.485,0.178-1.002,0.32-1.552l0.572-2.202H62.765z"/>
+			<path fill="#FFFFFF" d="M68.198,13.433H67.15v-2.7c0-0.832-0.316-1.248-0.95-1.248c-0.311,0-0.562,0.114-0.757,0.343
+				c-0.193,0.229-0.291,0.499-0.291,0.808v2.796h-1.048v-3.366c0-0.414-0.013-0.863-0.038-1.349h0.921l0.049,0.737h0.029
+				c0.122-0.229,0.304-0.418,0.543-0.569c0.284-0.176,0.602-0.265,0.95-0.265c0.44,0,0.806,0.142,1.097,0.427
+				c0.362,0.349,0.543,0.87,0.543,1.562V13.433z"/>
+			<path fill="#FFFFFF" d="M71.088,13.433h-1.047V6.556h1.047V13.433z"/>
+			<path fill="#FFFFFF" d="M77.258,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.01,0.718-1.727,0.718
+				c-0.693,0-1.244-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.711-0.698
+				c0.693,0,1.248,0.229,1.67,0.688C77.057,9.757,77.258,10.333,77.258,11.037z M76.17,11.071c0-0.435-0.094-0.808-0.281-1.119
+				c-0.219-0.376-0.533-0.564-0.939-0.564c-0.422,0-0.742,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
+				c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.713-0.191,0.939-0.574
+				C76.074,11.882,76.17,11.506,76.17,11.071z"/>
+			<path fill="#FFFFFF" d="M82.33,13.433h-0.941l-0.078-0.543h-0.029c-0.322,0.433-0.781,0.65-1.377,0.65
+				c-0.445,0-0.805-0.143-1.076-0.427c-0.246-0.258-0.369-0.579-0.369-0.96c0-0.576,0.24-1.015,0.723-1.319
+				c0.482-0.304,1.16-0.453,2.033-0.446V10.3c0-0.621-0.326-0.931-0.979-0.931c-0.465,0-0.875,0.117-1.229,0.349l-0.213-0.688
+				c0.438-0.271,0.979-0.407,1.617-0.407c1.232,0,1.85,0.65,1.85,1.95v1.736C82.262,12.78,82.285,13.155,82.33,13.433z
+				 M81.242,11.813v-0.727c-1.156-0.02-1.734,0.297-1.734,0.95c0,0.246,0.066,0.43,0.201,0.553c0.135,0.123,0.307,0.184,0.512,0.184
+				c0.23,0,0.445-0.073,0.641-0.218c0.197-0.146,0.318-0.331,0.363-0.558C81.236,11.946,81.242,11.884,81.242,11.813z"/>
+			<path fill="#FFFFFF" d="M88.285,13.433h-0.93l-0.049-0.757h-0.029c-0.297,0.576-0.803,0.864-1.514,0.864
+				c-0.568,0-1.041-0.223-1.416-0.669s-0.562-1.025-0.562-1.736c0-0.763,0.203-1.381,0.611-1.853c0.395-0.44,0.879-0.66,1.455-0.66
+				c0.633,0,1.076,0.213,1.328,0.64h0.02V6.556h1.049v5.607C88.248,12.622,88.26,13.045,88.285,13.433z M87.199,11.445v-0.786
+				c0-0.136-0.01-0.246-0.029-0.33c-0.059-0.252-0.186-0.464-0.379-0.635c-0.195-0.171-0.43-0.257-0.701-0.257
+				c-0.391,0-0.697,0.155-0.922,0.466c-0.223,0.311-0.336,0.708-0.336,1.193c0,0.466,0.107,0.844,0.322,1.135
+				c0.227,0.31,0.533,0.465,0.916,0.465c0.344,0,0.619-0.129,0.828-0.388C87.1,12.069,87.199,11.781,87.199,11.445z"/>
+			<path fill="#FFFFFF" d="M97.248,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.008,0.718-1.727,0.718
+				c-0.691,0-1.242-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.713-0.698
+				c0.691,0,1.248,0.229,1.668,0.688C97.047,9.757,97.248,10.333,97.248,11.037z M96.162,11.071c0-0.435-0.094-0.808-0.281-1.119
+				c-0.221-0.376-0.533-0.564-0.941-0.564c-0.42,0-0.74,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
+				c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.715-0.191,0.941-0.574
+				C96.064,11.882,96.162,11.506,96.162,11.071z"/>
+			<path fill="#FFFFFF" d="M102.883,13.433h-1.047v-2.7c0-0.832-0.316-1.248-0.951-1.248c-0.311,0-0.562,0.114-0.756,0.343
+				s-0.291,0.499-0.291,0.808v2.796h-1.049v-3.366c0-0.414-0.012-0.863-0.037-1.349h0.92l0.049,0.737h0.029
+				c0.123-0.229,0.305-0.418,0.543-0.569c0.285-0.176,0.602-0.265,0.951-0.265c0.439,0,0.805,0.142,1.096,0.427
+				c0.363,0.349,0.543,0.87,0.543,1.562V13.433z"/>
+			<path fill="#FFFFFF" d="M109.936,9.504h-1.154v2.29c0,0.582,0.205,0.873,0.611,0.873c0.188,0,0.344-0.016,0.467-0.049
+				l0.027,0.795c-0.207,0.078-0.479,0.117-0.814,0.117c-0.414,0-0.736-0.126-0.969-0.378c-0.234-0.252-0.35-0.676-0.35-1.271V9.504
+				h-0.689V8.719h0.689V7.855l1.027-0.31v1.173h1.154V9.504z"/>
+			<path fill="#FFFFFF" d="M115.484,13.433h-1.049v-2.68c0-0.845-0.316-1.268-0.949-1.268c-0.486,0-0.818,0.245-1,0.735
+				c-0.031,0.103-0.049,0.229-0.049,0.377v2.835h-1.047V6.556h1.047v2.841h0.02c0.33-0.517,0.803-0.775,1.416-0.775
+				c0.434,0,0.793,0.142,1.078,0.427c0.355,0.355,0.533,0.883,0.533,1.581V13.433z"/>
+			<path fill="#FFFFFF" d="M121.207,10.853c0,0.188-0.014,0.346-0.039,0.475h-3.143c0.014,0.466,0.164,0.821,0.455,1.067
+				c0.266,0.22,0.609,0.33,1.029,0.33c0.465,0,0.889-0.074,1.271-0.223l0.164,0.728c-0.447,0.194-0.973,0.291-1.582,0.291
+				c-0.73,0-1.305-0.215-1.721-0.645c-0.418-0.43-0.625-1.007-0.625-1.731c0-0.711,0.193-1.303,0.582-1.775
+				c0.406-0.504,0.955-0.756,1.648-0.756c0.678,0,1.193,0.252,1.541,0.756C121.068,9.77,121.207,10.265,121.207,10.853z
+				 M120.207,10.582c0.008-0.311-0.061-0.579-0.203-0.805c-0.182-0.291-0.459-0.437-0.834-0.437c-0.342,0-0.621,0.142-0.834,0.427
+				c-0.174,0.227-0.277,0.498-0.311,0.815H120.207z"/>
+		</g>
+	</g>
+</g>
+</svg>
diff --git a/themes/squares/client/src/img/stores/googleplay-badge.svg b/themes/squares/client/src/img/stores/googleplay-badge.svg
new file mode 100644
index 00000000..9e33e3aa
--- /dev/null
+++ b/themes/squares/client/src/img/stores/googleplay-badge.svg
@@ -0,0 +1,429 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   xml:space="preserve"
+   width="135.71649"
+   height="40.018951"
+   viewBox="0 0 135.71649 40.018951"
+   sodipodi:docname="google-play-badge.svg"><metadata
+     id="metadata8"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+     id="defs6"><linearGradient
+       x1="31.7997"
+       y1="183.2903"
+       x2="15.0173"
+       y2="166.5079"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
+       spreadMethod="pad"
+       id="linearGradient50"><stop
+         style="stop-opacity:1;stop-color:#00a0ff"
+         offset="0"
+         id="stop52" /><stop
+         style="stop-opacity:1;stop-color:#00a1ff"
+         offset="0.0066"
+         id="stop54" /><stop
+         style="stop-opacity:1;stop-color:#00beff"
+         offset="0.2601"
+         id="stop56" /><stop
+         style="stop-opacity:1;stop-color:#00d2ff"
+         offset="0.5122"
+         id="stop58" /><stop
+         style="stop-opacity:1;stop-color:#00dfff"
+         offset="0.7604"
+         id="stop60" /><stop
+         style="stop-opacity:1;stop-color:#00e3ff"
+         offset="1"
+         id="stop62" /></linearGradient><linearGradient
+       x1="43.8344"
+       y1="171.9986"
+       x2="19.637501"
+       y2="171.9986"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
+       spreadMethod="pad"
+       id="linearGradient68"><stop
+         style="stop-opacity:1;stop-color:#ffe000"
+         offset="0"
+         id="stop70" /><stop
+         style="stop-opacity:1;stop-color:#ffbd00"
+         offset="0.4087"
+         id="stop72" /><stop
+         style="stop-opacity:1;stop-color:#ffa500"
+         offset="0.7754"
+         id="stop74" /><stop
+         style="stop-opacity:1;stop-color:#ff9c00"
+         offset="1"
+         id="stop76" /></linearGradient><linearGradient
+       x1="34.827"
+       y1="169.7039"
+       x2="12.0687"
+       y2="146.9456"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
+       spreadMethod="pad"
+       id="linearGradient82"><stop
+         style="stop-opacity:1;stop-color:#ff3a44"
+         offset="0"
+         id="stop84" /><stop
+         style="stop-opacity:1;stop-color:#c31162"
+         offset="1"
+         id="stop86" /></linearGradient><linearGradient
+       x1="17.2973"
+       y1="191.82381"
+       x2="27.4599"
+       y2="181.6613"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
+       spreadMethod="pad"
+       id="linearGradient92"><stop
+         style="stop-opacity:1;stop-color:#32a071"
+         offset="0"
+         id="stop94" /><stop
+         style="stop-opacity:1;stop-color:#2da771"
+         offset="0.0685"
+         id="stop96" /><stop
+         style="stop-opacity:1;stop-color:#15cf74"
+         offset="0.4762"
+         id="stop98" /><stop
+         style="stop-opacity:1;stop-color:#06e775"
+         offset="0.8009"
+         id="stop100" /><stop
+         style="stop-opacity:1;stop-color:#00f076"
+         offset="1"
+         id="stop102" /></linearGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath110"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path112"
+         inkscape:connector-curvature="0" /></clipPath><mask
+       maskUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       id="mask114"><g
+         id="g116"><g
+           clip-path="url(#clipPath110)"
+           id="g118"><path
+             d="M 0,0 124,0 124,48 0,48 0,0 Z"
+             style="fill:#000000;fill-opacity:0.2;fill-rule:nonzero;stroke:none"
+             id="path120"
+             inkscape:connector-curvature="0" /></g></g></mask><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath126"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path128"
+         inkscape:connector-curvature="0" /></clipPath><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath130"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path132"
+         inkscape:connector-curvature="0" /></clipPath><pattern
+       patternTransform="matrix(1,0,0,-1,0,48)"
+       patternUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="124"
+       height="48"
+       id="pattern134"><g
+         id="g136" /><g
+         id="g138"><g
+           clip-path="url(#clipPath130)"
+           id="g140"><g
+             id="g142"><path
+               d="M 29.625,20.695 18.012,14.098 C 17.363,13.727 16.781,13.754 16.406,14.09 l -0.058,-0.063 0.058,-0.058 c 0.375,-0.336 0.957,-0.36 1.606,0.011 l 11.687,6.641 -0.074,0.074 z"
+               style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+               id="path144" /></g></g></g></pattern><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath158"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path160"
+         inkscape:connector-curvature="0" /></clipPath><mask
+       maskUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       id="mask162"><g
+         id="g164"><g
+           clip-path="url(#clipPath158)"
+           id="g166"><path
+             d="M 0,0 124,0 124,48 0,48 0,0 Z"
+             style="fill:#000000;fill-opacity:0.12000002;fill-rule:nonzero;stroke:none"
+             id="path168"
+             inkscape:connector-curvature="0" /></g></g></mask><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath174"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path176"
+         inkscape:connector-curvature="0" /></clipPath><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath178"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path180"
+         inkscape:connector-curvature="0" /></clipPath><pattern
+       patternTransform="matrix(1,0,0,-1,0,48)"
+       patternUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="124"
+       height="48"
+       id="pattern182"><g
+         id="g184" /><g
+         id="g186"><g
+           clip-path="url(#clipPath178)"
+           id="g188"><g
+             id="g190"><path
+               d="m 16.348,14.145 c -0.235,0.246 -0.371,0.628 -0.371,1.125 l 0,-0.118 c 0,-0.496 0.136,-0.879 0.371,-1.125 l 0.058,0.063 -0.058,0.055 z"
+               style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+               id="path192" /></g></g></g></pattern><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath206"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path208"
+         inkscape:connector-curvature="0" /></clipPath><mask
+       maskUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       id="mask210"><g
+         id="g212"><g
+           clip-path="url(#clipPath206)"
+           id="g214"><path
+             d="M 0,0 124,0 124,48 0,48 0,0 Z"
+             style="fill:#000000;fill-opacity:0.12000002;fill-rule:nonzero;stroke:none"
+             id="path216"
+             inkscape:connector-curvature="0" /></g></g></mask><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath222"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path224"
+         inkscape:connector-curvature="0" /></clipPath><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath226"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path228"
+         inkscape:connector-curvature="0" /></clipPath><pattern
+       patternTransform="matrix(1,0,0,-1,0,48)"
+       patternUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="124"
+       height="48"
+       id="pattern230"><g
+         id="g232" /><g
+         id="g234"><g
+           clip-path="url(#clipPath226)"
+           id="g236"><g
+             id="g238"><path
+               d="m 33.613,22.961 -3.988,-2.266 0.074,-0.074 3.914,2.223 c 0.559,0.316 0.836,0.734 0.836,1.156 -0.047,-0.379 -0.332,-0.75 -0.836,-1.039 z"
+               style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+               id="path240" /></g></g></g></pattern><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath254"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path256"
+         inkscape:connector-curvature="0" /></clipPath><mask
+       maskUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       id="mask258"><g
+         id="g260"><g
+           clip-path="url(#clipPath254)"
+           id="g262"><path
+             d="M 0,0 124,0 124,48 0,48 0,0 Z"
+             style="fill:#000000;fill-opacity:0.25;fill-rule:nonzero;stroke:none"
+             id="path264"
+             inkscape:connector-curvature="0" /></g></g></mask><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath270"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path272"
+         inkscape:connector-curvature="0" /></clipPath><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath274"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path276"
+         inkscape:connector-curvature="0" /></clipPath><pattern
+       patternTransform="matrix(1,0,0,-1,0,48)"
+       patternUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="124"
+       height="48"
+       id="pattern278"><g
+         id="g280" /><g
+         id="g282"><g
+           clip-path="url(#clipPath274)"
+           id="g284"><g
+             id="g286"><path
+               d="m 18.012,33.902 15.601,-8.863 c 0.508,-0.289 0.789,-0.66 0.836,-1.039 0,0.418 -0.277,0.836 -0.836,1.156 L 18.012,34.02 c -1.117,0.632 -2.035,0.105 -2.035,-1.176 l 0,-0.114 c 0,1.278 0.918,1.805 2.035,1.172 z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+               id="path288" /></g></g></g></pattern></defs><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1366"
+     inkscape:window-height="705"
+     id="namedview4"
+     showgrid="false"
+     inkscape:zoom="7.6276974"
+     inkscape:cx="93.965168"
+     inkscape:cy="29.61582"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g10" /><g
+     id="g10"
+     inkscape:groupmode="layer"
+     inkscape:label="google-play-badge"
+     transform="matrix(1.25,0,0,-1.25,-9.4247625,49.85025)"><g
+       id="g12"
+       transform="matrix(1.0023923,0,0,0.99072975,-0.29664807,0)"><path
+         d="M 112,8 12,8 C 9.801,8 8,9.801 8,12 l 0,24 c 0,2.199 1.801,4 4,4 l 100,0 c 2.199,0 4,-1.801 4,-4 l 0,-24 c 0,-2.199 -1.801,-4 -4,-4 z"
+         style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path14"
+         inkscape:connector-curvature="0" /><path
+         d="m 112,39.359 c 1.852,0 3.359,-1.507 3.359,-3.359 l 0,-24 c 0,-1.852 -1.507,-3.359 -3.359,-3.359 l -100,0 c -1.852,0 -3.359,1.507 -3.359,3.359 l 0,24 c 0,1.852 1.507,3.359 3.359,3.359 l 100,0 M 112,40 12,40 C 9.801,40 8,38.199 8,36 L 8,12 C 8,9.801 9.801,8 12,8 l 100,0 c 2.199,0 4,1.801 4,4 l 0,24 c 0,2.199 -1.801,4 -4,4 z"
+         style="fill:#a6a6a6;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path16"
+         inkscape:connector-curvature="0" /><g
+         id="g18"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 45.934,16.195 c 0,0.668 -0.2,1.203 -0.594,1.602 -0.453,0.473 -1.043,0.711 -1.766,0.711 -0.691,0 -1.281,-0.242 -1.765,-0.719 -0.485,-0.484 -0.727,-1.078 -0.727,-1.789 0,-0.711 0.242,-1.305 0.727,-1.785 0.484,-0.481 1.074,-0.723 1.765,-0.723 0.344,0 0.672,0.071 0.985,0.203 0.312,0.133 0.566,0.313 0.75,0.535 l -0.418,0.422 c -0.321,-0.379 -0.758,-0.566 -1.317,-0.566 -0.504,0 -0.941,0.176 -1.312,0.531 -0.367,0.356 -0.551,0.817 -0.551,1.383 0,0.566 0.184,1.031 0.551,1.387 0.371,0.351 0.808,0.531 1.312,0.531 0.535,0 0.985,-0.18 1.34,-0.535 0.234,-0.235 0.367,-0.559 0.402,-0.973 l -1.742,0 0,-0.578 2.324,0 c 0.028,0.125 0.036,0.246 0.036,0.363 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path20"
+           inkscape:connector-curvature="0" /></g><g
+         id="g22"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 49.621,14.191 -2.183,0 0,1.52 1.968,0 0,0.578 -1.968,0 0,1.52 2.183,0 0,0.589 -2.801,0 0,-4.796 2.801,0 0,0.589 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path24"
+           inkscape:connector-curvature="0" /></g><g
+         id="g26"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 52.223,18.398 -0.618,0 0,-4.207 -1.339,0 0,-0.589 3.297,0 0,0.589 -1.34,0 0,4.207 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path28"
+           inkscape:connector-curvature="0" /></g><g
+         id="g30"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 55.949,18.398 0,-4.796 0.617,0 0,4.796 -0.617,0 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path32"
+           inkscape:connector-curvature="0" /></g><g
+         id="g34"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 59.301,18.398 -0.613,0 0,-4.207 -1.344,0 0,-0.589 3.301,0 0,0.589 -1.344,0 0,4.207 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path36"
+           inkscape:connector-curvature="0" /></g><g
+         id="g38"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 66.887,17.781 c -0.473,0.485 -1.059,0.727 -1.758,0.727 -0.703,0 -1.289,-0.242 -1.762,-0.727 C 62.895,17.297 62.66,16.703 62.66,16 c 0,-0.703 0.235,-1.297 0.707,-1.781 0.473,-0.485 1.059,-0.727 1.762,-0.727 0.695,0 1.281,0.242 1.754,0.731 0.476,0.488 0.711,1.078 0.711,1.777 0,0.703 -0.235,1.297 -0.707,1.781 z m -3.063,-0.402 c 0.356,0.359 0.789,0.539 1.305,0.539 0.512,0 0.949,-0.18 1.301,-0.539 0.355,-0.359 0.535,-0.82 0.535,-1.379 0,-0.559 -0.18,-1.02 -0.535,-1.379 -0.352,-0.359 -0.789,-0.539 -1.301,-0.539 -0.516,0 -0.949,0.18 -1.305,0.539 -0.355,0.359 -0.535,0.82 -0.535,1.379 0,0.559 0.18,1.02 0.535,1.379 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path40"
+           inkscape:connector-curvature="0" /></g><g
+         id="g42"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 68.461,18.398 0,-4.796 0.75,0 2.332,3.73 0.027,0 -0.027,-0.922 0,-2.808 0.617,0 0,4.796 -0.644,0 -2.442,-3.914 -0.027,0 0.027,0.926 0,2.988 -0.613,0 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path44"
+           inkscape:connector-curvature="0" /></g><path
+         d="m 62.508,22.598 c -1.879,0 -3.414,-1.43 -3.414,-3.403 0,-1.957 1.535,-3.402 3.414,-3.402 1.883,0 3.418,1.445 3.418,3.402 0,1.973 -1.535,3.403 -3.418,3.403 z m 0,-5.465 c -1.031,0 -1.918,0.851 -1.918,2.062 0,1.227 0.887,2.063 1.918,2.063 1.031,0 1.922,-0.836 1.922,-2.063 0,-1.211 -0.891,-2.062 -1.922,-2.062 z m -7.449,5.465 c -1.883,0 -3.414,-1.43 -3.414,-3.403 0,-1.957 1.531,-3.402 3.414,-3.402 1.882,0 3.414,1.445 3.414,3.402 0,1.973 -1.532,3.403 -3.414,3.403 z m 0,-5.465 c -1.032,0 -1.922,0.851 -1.922,2.062 0,1.227 0.89,2.063 1.922,2.063 1.031,0 1.918,-0.836 1.918,-2.063 0,-1.211 -0.887,-2.062 -1.918,-2.062 z m -8.864,4.422 0,-1.446 3.453,0 c -0.101,-0.808 -0.371,-1.402 -0.785,-1.816 -0.504,-0.5 -1.289,-1.055 -2.668,-1.055 -2.125,0 -3.789,1.715 -3.789,3.84 0,2.125 1.664,3.84 3.789,3.84 1.149,0 1.985,-0.449 2.602,-1.031 l 1.019,1.019 c -0.863,0.824 -2.011,1.457 -3.621,1.457 -2.914,0 -5.363,-2.371 -5.363,-5.285 0,-2.914 2.449,-5.285 5.363,-5.285 1.575,0 2.758,0.516 3.688,1.484 0.953,0.953 1.25,2.293 1.25,3.375 0,0.336 -0.028,0.645 -0.078,0.903 l -4.86,0 z m 36.246,-1.121 c -0.281,0.761 -1.148,2.164 -2.914,2.164 -1.75,0 -3.207,-1.379 -3.207,-3.403 0,-1.906 1.442,-3.402 3.375,-3.402 1.563,0 2.465,0.953 2.836,1.508 l -1.16,0.773 c -0.387,-0.566 -0.914,-0.941 -1.676,-0.941 -0.757,0 -1.3,0.347 -1.648,1.031 l 4.551,1.883 -0.157,0.387 z m -4.64,-1.133 c -0.039,1.312 1.019,1.984 1.777,1.984 0.594,0 1.098,-0.297 1.266,-0.722 L 77.801,19.301 Z M 74.102,16 l 1.496,0 0,10 -1.496,0 0,-10 z m -2.45,5.84 -0.05,0 c -0.336,0.398 -0.977,0.758 -1.789,0.758 -1.704,0 -3.262,-1.496 -3.262,-3.414 0,-1.907 1.558,-3.391 3.262,-3.391 0.812,0 1.453,0.363 1.789,0.773 l 0.05,0 0,-0.488 c 0,-1.301 -0.695,-2 -1.816,-2 -0.914,0 -1.481,0.66 -1.715,1.215 L 66.82,14.75 c 0.375,-0.902 1.368,-2.012 3.016,-2.012 1.754,0 3.234,1.032 3.234,3.543 l 0,6.11 -1.418,0 0,-0.551 z m -1.711,-4.707 c -1.031,0 -1.894,0.863 -1.894,2.051 0,1.199 0.863,2.074 1.894,2.074 1.016,0 1.817,-0.875 1.817,-2.074 0,-1.188 -0.801,-2.051 -1.817,-2.051 z M 89.445,26 l -3.578,0 0,-10 1.492,0 0,3.789 2.086,0 c 1.657,0 3.282,1.199 3.282,3.106 0,1.906 -1.629,3.105 -3.282,3.105 z m 0.039,-4.82 -2.125,0 0,3.429 2.125,0 c 1.114,0 1.75,-0.925 1.75,-1.714 0,-0.774 -0.636,-1.715 -1.75,-1.715 z m 9.223,1.437 c -1.078,0 -2.199,-0.476 -2.66,-1.531 l 1.324,-0.555 c 0.285,0.555 0.809,0.735 1.363,0.735 0.774,0 1.559,-0.465 1.571,-1.286 l 0,-0.105 c -0.27,0.156 -0.848,0.387 -1.559,0.387 -1.426,0 -2.879,-0.785 -2.879,-2.25 0,-1.34 1.168,-2.203 2.481,-2.203 1.004,0 1.558,0.453 1.906,0.98 l 0.051,0 0,-0.773 1.441,0 0,3.836 c 0,1.773 -1.324,2.765 -3.039,2.765 z m -0.18,-5.48 c -0.488,0 -1.168,0.242 -1.168,0.847 0,0.774 0.848,1.071 1.582,1.071 0.657,0 0.965,-0.145 1.364,-0.336 -0.117,-0.926 -0.914,-1.582 -1.778,-1.582 z m 8.469,5.261 -1.715,-4.335 -0.051,0 -1.773,4.335 -1.609,0 2.664,-6.058 -1.52,-3.371 1.559,0 4.105,9.429 -1.66,0 z M 93.547,16 l 1.496,0 0,10 -1.496,0 0,-10 z"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path46"
+         inkscape:connector-curvature="0" /><g
+         id="g48"><path
+           d="M 16.348,33.969 C 16.113,33.723 15.977,33.34 15.977,32.844 l 0,-17.692 c 0,-0.496 0.136,-0.879 0.371,-1.125 l 0.058,-0.054 9.914,9.91 0,0.234 -9.914,9.91 -0.058,-0.058 z"
+           style="fill:url(#linearGradient50);fill-opacity:1;fill-rule:nonzero;stroke:none"
+           id="path64"
+           inkscape:connector-curvature="0" /></g><g
+         id="g66"><path
+           d="m 29.621,20.578 -3.301,3.305 0,0.234 3.305,3.305 0.074,-0.043 3.914,-2.227 c 1.117,-0.632 1.117,-1.672 0,-2.308 l -3.914,-2.223 -0.078,-0.043 z"
+           style="fill:url(#linearGradient68);fill-opacity:1;fill-rule:nonzero;stroke:none"
+           id="path78"
+           inkscape:connector-curvature="0" /></g><g
+         id="g80"><path
+           d="M 29.699,20.621 26.32,24 16.348,14.027 c 0.371,-0.39 0.976,-0.437 1.664,-0.047 l 11.687,6.641"
+           style="fill:url(#linearGradient82);fill-opacity:1;fill-rule:nonzero;stroke:none"
+           id="path88"
+           inkscape:connector-curvature="0" /></g><g
+         id="g90"><path
+           d="M 29.699,27.379 18.012,34.02 c -0.688,0.386 -1.293,0.339 -1.664,-0.051 L 26.32,24 l 3.379,3.379 z"
+           style="fill:url(#linearGradient92);fill-opacity:1;fill-rule:nonzero;stroke:none"
+           id="path104"
+           inkscape:connector-curvature="0" /></g><g
+         id="g106"><g
+           id="g108" /><g
+           id="g122"
+           mask="url(#mask114)"><g
+             id="g124" /><g
+             id="g146"><g
+               clip-path="url(#clipPath126)"
+               id="g148"><g
+                 id="g150"><path
+                   d="M 0,0 124,0 124,48 0,48 0,0 Z"
+                   style="fill:url(#pattern134);fill-opacity:1;fill-rule:nonzero;stroke:none"
+                   id="path152"
+                   inkscape:connector-curvature="0" /></g></g></g></g></g><g
+         id="g154"><g
+           id="g156" /><g
+           id="g170"
+           mask="url(#mask162)"><g
+             id="g172" /><g
+             id="g194"><g
+               clip-path="url(#clipPath174)"
+               id="g196"><g
+                 id="g198"><path
+                   d="M 0,0 124,0 124,48 0,48 0,0 Z"
+                   style="fill:url(#pattern182);fill-opacity:1;fill-rule:nonzero;stroke:none"
+                   id="path200"
+                   inkscape:connector-curvature="0" /></g></g></g></g></g><g
+         id="g202"><g
+           id="g204" /><g
+           id="g218"
+           mask="url(#mask210)"><g
+             id="g220" /><g
+             id="g242"><g
+               clip-path="url(#clipPath222)"
+               id="g244"><g
+                 id="g246"><path
+                   d="M 0,0 124,0 124,48 0,48 0,0 Z"
+                   style="fill:url(#pattern230);fill-opacity:1;fill-rule:nonzero;stroke:none"
+                   id="path248"
+                   inkscape:connector-curvature="0" /></g></g></g></g></g><g
+         id="g250"><g
+           id="g252" /><g
+           id="g266"
+           mask="url(#mask258)"><g
+             id="g268" /><g
+             id="g290"><g
+               clip-path="url(#clipPath270)"
+               id="g292"><g
+                 id="g294"><path
+                   d="M 0,0 124,0 124,48 0,48 0,0 Z"
+                   style="fill:url(#pattern278);fill-opacity:1;fill-rule:nonzero;stroke:none"
+                   id="path296"
+                   inkscape:connector-curvature="0" /></g></g></g></g></g></g></g></svg>
\ No newline at end of file
diff --git a/themes/squares/client/src/img/success.png b/themes/squares/client/src/img/success.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee9d6841bbb7208220a831c1c42917eb8facffa5
GIT binary patch
literal 3147
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

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/user.png b/themes/squares/client/src/img/user.png
new file mode 100644
index 0000000000000000000000000000000000000000..00941399d34f0f2d85d323f61daa67c2c00c494d
GIT binary patch
literal 2933
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

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/img/warning.png b/themes/squares/client/src/img/warning.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6acd953b142a8e43e293c353a60e6313a8d39eb
GIT binary patch
literal 4038
zcmZ8kc{CK>7k@Km%nZhmB)g(al4J@g%*Ybi@{R0UV<~HPGGh#)vXiopt-hj2MImcp
zY$;2&EGf;GWSK0JWq#AYzjw}k@4R>Jx$oW2UEdvZQv)=Q7!LpdZFo`Fg0oWo5|W!U
zw`;lda28mgj^Py~XG9{237kF3|DsJG09+w|2|^mC_HqtIgY>L}{_%4U3Uvu^1EHaz
zO4oh80<XFFyD9kvc;x;%F9rbSf}t+qO4#^P{w;r@PRY(eIg=cN!P7p2hPVtv`Fv9q
zpU2sTk8yp*j|NS}6Z_h;g3{gQMza*h(iO$MTRNP@xuRoZ4Ui^b-yqSk2Kx%a_h;Y_
zy=!f3I}TGGZG4`TWTF!`xAaUg`su{<vtL2!W}o&}^FK07Jy=bJ=XQNJ8yf0lVtoaT
z?C)gA00DUdkdXrd`v4LJVqriBO40rJ;)BuVJ`~vZVe3kzMr8##?Q=Pl@=kgBZZMh`
zey7nx7`&@vnL+0_V`mH@_#J3<nhO~(4hO5Z77Y${D}aQP%;i>%A^>WZHKCL(h;uQN
z$(4fz2^gkydwnSstYvq?z;jH?4dHpjOIsi?F~5mv-=X~B1+IfT=c@TV0XW+s38jp3
zIm`8ol6U1Y3*FZ4G|uCdEr5XCymsumB+=8f9+kg$8eP;^u9f!rn<h8-W{p7sR|F+g
z28Y08n)l@7&LecEWG4;^G|GYmG_$Z{ftqOlr-tB8#uLT4KtS(fIQXN=wyIo8y;I94
zRR70K0SU(RH(c=eWKspdHv@I+_md;=>ogW5jie4Qx&UZq%;ioEF43zF#7yy~zM-eI
zO?nd_=$d}*1Zdj89sj6I?h<22KC>b!%CwF;CBL2iff7f&(SHL%Y3gOm9%rm>z`BQ<
zags$B!oM7@imA=`0F#zKMyK{>K{K;0WBK$OE(4l07$MAiJTzu}?T}$Eu6~)JEgc2l
zf=SEgU@ShC_l!E7E{fG`pTiY)xdsO#!EcwQcM9VpAIi>eEYTaUlkwl)(ssK%=%={(
zyLCh_G^+Jtx;(pcl>ozzx!j?_lkK#SDH7&caoLxQxBIRw{7*%VTL*&Jr`VX~UcGAs
z20}q%Os|<yy7`=u(d?#=DjygdVS2bu2Qs?V-eAu)AY)sIA>>1VA<m4t7c2;Mc0W?K
zGU0N&50mioLhmx9dmiI01HL_mh{Q&T09|Q{TPo|(6%?ttz+y|X>pPeKz|^ki=-gYS
z9Y@17L~Ki*qd&(F9GL7rg&nPk`xaYE{WG~yqtOG|AGd;=yeICQ3?P8Wl(<KYJP>?P
zFZ<MpR##+bEh?BKOx!!bryHYrnt87C(2(Po?NcACvW1ugup;}_&0b*Y1V}g!+O4m$
zr^+(hmUAlwdaD?!+O!hKF|tU=i)H9h`g0>7V8e_`3g%Z~$IWK8`8B+2Uaw1D@FC+7
z-$J8b@&2r&R`X87_vAo=1n`SqVsgn1)n)~(GkKZ^+y9#v%9lDe!PdCl<}d?4l&Z)=
zLn&Pk*;lOwuF)@Xq&>Lrv$yIxx<e8BR_JBja}1dj(g5&w%&1sPOm?AH8B8-QZdy=H
zNAgT07+3h-*=lb5B&Zc$G)$V3JL@L|JP~-Mij|6}_<+Ufb1>EBk9P*f6q;C&Gxctr
z>UH@f5#<~SKVI<i6x+kPFO_wT$K*E+u{a`azZ!qI``T>H7{8TvYp}h5)?t8@rj)0$
z=(|UP)`a4gb}Nfh$}-Aot~qRv-2NG&Pw?!YJ_L{-cI0;gx6C17f#<(JU-shN4s}Xm
zsr`bk<+y&nAzmdTfd%Fm{E6d?o6#b_8AlL0OZR6X0~$FN0^=Va&iV>~OZ62FfY!fr
z+G@7W*EE5ULNUF6+k=dc=OuP(y~w^QFmG}IU~z1DYm-#g5&k$r%25&3Dt&RF(P6Qy
z!hED53NQMhpoEN40!T@U!U(PZ!f|Zebkh%KFy7&Ljg0S%@80Y9{`L*_p0Gq(-Xa_n
zWwVhZa3tlb{&BvDJc*;YmC03wmleLmlfhCIJloH!f053eR9|}uD3}(DuE;rORKDF~
z8OF`gF;dSjm&vc;j;H-8dLiCxQey{Hz=`iQ)BzERRTZji7F7reYFTV&VRD5#mM;P(
zOnzY7S=<_<xI)1+CjAsrfyL+5G>1pZRu#%rT>*GCwrZtj>RI@Q?16O+_|h&X4@_qT
z{7RG|7N$Y~RTfv^0XLX@uwvGl4+Up|W7er#Ssc0MwmZo`pUyz>8<(%}N)*8iLT<*$
zO7nn-T=u0({;o{JGM*TzB-T(t)5aqsCE#OXne$tyGP|&oKl$KN3NP@5DkD^g0|1p@
z4(AS`US%VnTz+8Z^%`KGKzv~a`sOjR&m!IZiOqmEvGch4*@fGRIo{5gpM0we7|#}f
z^CTPH)gw)SBkBRwS>h6OBTukHNYUs@{TU=wUvp<|w38<yPtir}zAqg>svkaGfXF17
zWRj^r4BA@-?+x!d<g|u(9p*}XUoYubP^HWu4E67!KfFLg*c?QycwI^^ZICqHw5BzV
zp-@0;R{OmMJp0sm$zjILq4qQ7rC0>a`Cm2p9d}nF!q(p6<V=^}K0yk-lGeGJBFwFs
zVB7tg+cK#p8YCX8i^H_s**CUX-;GXIuanykwuo%0MHS*Z@wxrnn!cxZR&MPaHS?!}
zM&+p@6A!^7>NOeC5{5={Kr7n#qM8v0M{@o_vGlW5W}(rbH_w)a22X{DsMW>q`VKlT
zXk-Y086nM7%Po?soy))lmj}z#H&QEp9}4F_KI_@4j-*^)ygEts*RkiKn62eo$G03-
zOi`)M-~=((Ir@vJgpNw<D=_HFY=ho71i%cYBC`c0=qvWE@LAF-Uklc1pv2`(8!bLv
zP>W2eeFB3y_uX|F=Qc<du0SOZbsPa(Fzv)(DGk=(@hkldLDzL@^WAzu84z5E{(Jz?
zWF}u`N=mebMmN8c1<M5aDP?P&{6&lV97{61gS&1={^P_At=_IsS%P>fJs>&llMuD-
z;!BoDIu2Ioq}!HTPb2l-x0v3yVVB%8h?y?A85*|%wy3iFW1CtV+P>P+YD!=Qp&6nh
zpI<pzgk=;}FKJtQq+E#xdDklw`2gOZ_-6|`0tWAGJ)TJp11<0C|Ad~fI#ptDaBk;W
zZL33@h+T(1H*oSVHQ)FWH3Kt9Ouu&1n1j^<m-W&Bs?fYRKtH8m!MtGeahy&@Mqe7P
z@YGz_W9Gruv|b&RxvphbQOai-CgRb>e}j|SXo%pJ_yAdzPv0Hd-05QBz!d!eVX!e_
z-js2+CCJYh%8^@Wg9~fkF6Qu>bT&Ro^dIN&*5>tgan0OP%w$V%fA-%-^*%>}*H>=U
z!jTax<J;|ufJ@;-Rwzm&yrzT;2uNg`xAG*v?ti}@G?tAo&WmOuG~2wvLiqwu!_RY;
zi7)jKKt}!3^P~wgW!zCPb7I(W)=KdAKv?f|j}Yg!jE?Vm7@%T)KcLR;Pfm;)jurD9
zv@m;Vodyb2sTm<keP^vrRaZl-Q@(|O9=PaFe0W~H=ELoPz_*pz^u-`F@GIXqdxpa+
z_8V|}&5FmZxw*NA7xRJvv+8R{N@V1AVfx$J>wGN=$~V1I2jb9T`^h|OQK?o+N?D|4
z%z=m!488|@gJ9jym%RV<;yy5CvE=1Zp4VzOXMit87iM+GU@MG@F&tH|m(TSZJC+>>
zVV7QsOjvSjJ`@G(vuKq+ovB&m7i#;On+EK^sDZq)O`%>Aot~S$<ERNyrV9U7Un%OQ
z#dC||FE%W6uar1FGy4`?_%KRV-9m1gt+X@?H;|BCg#NvGQ2+YCRF#Wts;}OE@}sXG
zp2M*+A^@;|@<sWgPJUs$;BRNc3Y+HN#q%mR{7WASN~9^W;<xt$@3m=bT-eD%DgA*M
zd1sivaRot2;%plxS$!J@*cM9@GEB(}#td6^0;V9xphFrE?Pbe+oNu6U>T6tk$6miZ
z37nON_ttN@aBChO!q9G=2`91_S^1OMbP-+Qzb~7M7k%ZmY*tzbI>2DcboU%unRGe_
z^g6>eZQIZhx<$yz=BE-TqN+ya*KDr>e5)V=r(T0o09y}kAUswax2W8N6l5)zy{FXT
zBCBMbJKg8c8{RJV4?lJfI=NJj3;36BD84{BOqIIaih|ifAHPMW@E@HB`s-w(SAV(o
z8BYmb{tLZdgx&WRQ_Flp++DL9J%{!bU5L_O@OV^m-WN_@GfqN+t#6a>ZJ)fIEc54H
z_Ahf?jW>B!W)Hfw>LIikDG8h3(g;8IH78vtMT}9pZk9gqaafF_b|zx9h71z{H7U<{
z+UCstV>w|G^N5qtm{_m!4V!Cc_#$GTw0C<j8RxB*a(R-^m`IEnM5V;7brkvZ_~bzz
zZFszdIg4_-)b`t0VX_z>kB!PSFBD(<UyTDmCTBj6k$rp`0yu_$?wjr9_*tD%r0a3t
z>8Zm(v9+Jmfk6V{CR2+G|9<qF6L2%!9&`%uIC_$o7eog5dYl;k^{U+diNo=@=lhV^
zs#&LjVA|(;KS>x>_Jus|!Ap5u#<Va$r&A-O>`Lp(Fwz*3I*OOGUT(q-M!Afy{tE@U
zJ2F=Cu$Ig<BwTa<8E#?u0|2kxdRvcEE~JM5P#P{!y{IFZ#uI_P{14$P1aI@HX^|o+
z{D#-2H7(}u-?(k^wuQw=UB<%ZPN{!-S->fDXw+ZF2+tPekHA`bg@1(%I2vkB|8wwf
zRU;Ls;@*5M8xL*Febm=t`gFhF?0_;=b~>tWNj@wC1yr}RMl6<w^)4eLu)l;><c<P7
z;dOvgsQX&H4KQ|b*cG2r*41JZS@T<-px!*xoxb6-{<DEX4Dl_2&Nw~U<q0H|y~>=>
zX6-I}2_osksR_lEmhYexY@erD5yZS_@mPt{mq!Rag+PIruP1DNV3J?1b5O|b#ua+1
zKK*Qlxf{Zjnf!_bXY<cUJ8RGpl&JEJ4s#&W*IaSEr*45jT0i(iHqFiJg^u#HHtsAc
zdD0AYVcze|jA=Sms1zSLDH>F=)Kg>LK0X;cl;x&6Us;m43gbnA<C##JL0Ksgs|C5E
z6d2L^pPe->ydy`^s!rJ|y34o}Mlo51w#*DkxKu*ZSO^rjzY4`S%iTMPk?%#D9Hq0u
zXO7WS9c7da`CWXmSEK4H`Zo6u$TGFji_X0wF<dHft$bx3Vb3nQ|KRuD<1+V?v6=X>
zYHis}o|pf`w?RCV<8AhzFDtAF=BVX(@&7)kpvZHP-ER?mQZc1eo%81ghI*#D&vcxr
F{{xrIJYfI;

literal 0
HcmV?d00001

diff --git a/themes/squares/client/src/index.ts b/themes/squares/client/src/index.ts
new file mode 100644
index 00000000..802004a8
--- /dev/null
+++ b/themes/squares/client/src/index.ts
@@ -0,0 +1,34 @@
+
+import FirstFactorValidator = require("./lib/firstfactor/FirstFactorValidator");
+
+import FirstFactor from "./lib/firstfactor/index";
+import SecondFactor from "./lib/secondfactor/index";
+import TOTPRegister from "./lib/totp-register/totp-register";
+import U2fRegister from "./lib/u2f-register/u2f-register";
+import ResetPasswordRequest from "./lib/reset-password/reset-password-request";
+import ResetPasswordForm from "./lib/reset-password/reset-password-form";
+import jslogger = require("js-logger");
+import jQuery = require("jquery");
+import Endpoints = require("../../shared/api");
+
+jslogger.useDefaults();
+jslogger.setLevel(jslogger.INFO);
+
+(function () {
+  (<any>window).jQuery = jQuery;
+  require("bootstrap");
+
+  jQuery('[data-toggle="tooltip"]').tooltip();
+  if (window.location.pathname == Endpoints.FIRST_FACTOR_GET)
+    FirstFactor(window, jQuery, FirstFactorValidator, jslogger);
+  else if (window.location.pathname == Endpoints.SECOND_FACTOR_GET)
+    SecondFactor(window, jQuery, (global as any).u2f);
+  else if (window.location.pathname == Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET)
+    TOTPRegister(window, jQuery);
+  else if (window.location.pathname == Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET)
+    U2fRegister(window, jQuery, (global as any).u2f);
+  else if (window.location.pathname == Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET)
+    ResetPasswordForm(window, jQuery);
+  else if (window.location.pathname == Endpoints.RESET_PASSWORD_REQUEST_GET)
+    ResetPasswordRequest(window, jQuery);
+})();
diff --git a/themes/squares/client/src/lib/GetPromised.ts b/themes/squares/client/src/lib/GetPromised.ts
new file mode 100644
index 00000000..77913965
--- /dev/null
+++ b/themes/squares/client/src/lib/GetPromised.ts
@@ -0,0 +1,14 @@
+import BluebirdPromise = require("bluebird");
+
+export default function ($: JQueryStatic, url: string, data: Object, fn: any,
+  dataType: string): BluebirdPromise<any> {
+  return new BluebirdPromise<any>((resolve, reject) => {
+    $.get(url, {}, undefined, dataType)
+      .done((data: any) => {
+        resolve(data);
+      })
+      .fail((xhr: JQueryXHR, textStatus: string) => {
+        reject(textStatus);
+      });
+  });
+}
\ No newline at end of file
diff --git a/themes/squares/client/src/lib/INotifier.ts b/themes/squares/client/src/lib/INotifier.ts
new file mode 100644
index 00000000..df947538
--- /dev/null
+++ b/themes/squares/client/src/lib/INotifier.ts
@@ -0,0 +1,14 @@
+
+declare type Handler = () => void;
+
+export interface Handlers {
+  onFadedIn: Handler;
+  onFadedOut: Handler;
+}
+
+export interface INotifier {
+  success(msg: string, handlers?: Handlers): void;
+  error(msg: string, handlers?: Handlers): void;
+  warning(msg: string, handlers?: Handlers): void;
+  info(msg: string, handlers?: Handlers): void;
+}
\ No newline at end of file
diff --git a/themes/squares/client/src/lib/Notifier.ts b/themes/squares/client/src/lib/Notifier.ts
new file mode 100644
index 00000000..c0252b9b
--- /dev/null
+++ b/themes/squares/client/src/lib/Notifier.ts
@@ -0,0 +1,83 @@
+
+
+import util = require("util");
+import { INotifier, Handlers } from "./INotifier";
+
+class NotificationEvent {
+  private element: JQuery;
+  private message: string;
+  private statusType: string;
+  private timeoutId: any;
+
+  constructor(element: JQuery, msg: string, statusType: string) {
+    this.message = msg;
+    this.statusType = statusType;
+    this.element = element;
+  }
+
+  private clearNotification() {
+    this.element.removeClass(this.statusType);
+    this.element.html("");
+  }
+
+  start(handlers?: Handlers) {
+    const that = this;
+    const FADE_TIME = 500;
+    const html = util.format('<i><img src="/img/notifications/%s.png" alt="status %s"/></i>\
+      <span>%s</span>', this.statusType, this.statusType, this.message);
+    this.element.html(html);
+    this.element.addClass(this.statusType);
+    this.element.fadeIn(FADE_TIME, function () {
+      if (handlers)
+        handlers.onFadedIn();
+    });
+
+    this.timeoutId = setTimeout(function () {
+      that.element.fadeOut(FADE_TIME, function () {
+        that.clearNotification();
+        if (handlers)
+          handlers.onFadedOut();
+      });
+    }, 4000);
+  }
+
+  interrupt() {
+    this.clearNotification();
+    this.element.hide();
+    clearTimeout(this.timeoutId);
+  }
+}
+
+export class Notifier implements INotifier {
+  private element: JQuery;
+  private onGoingEvent: NotificationEvent;
+
+  constructor(selector: string, $: JQueryStatic) {
+    this.element = $(selector);
+    this.onGoingEvent = undefined;
+  }
+
+  private displayAndFadeout(msg: string, statusType: string, handlers?: Handlers): void {
+    if (this.onGoingEvent)
+      this.onGoingEvent.interrupt();
+
+    this.onGoingEvent = new NotificationEvent(this.element, msg, statusType);
+    this.onGoingEvent.start(handlers);
+  }
+
+  success(msg: string, handlers?: Handlers) {
+    this.displayAndFadeout(msg, "success", handlers);
+  }
+
+  error(msg: string, handlers?: Handlers) {
+    this.displayAndFadeout(msg, "error", handlers);
+  }
+
+  warning(msg: string, handlers?: Handlers) {
+    this.displayAndFadeout(msg, "warning", handlers);
+  }
+
+  info(msg: string, handlers?: Handlers) {
+    this.displayAndFadeout(msg, "info", handlers);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/client/src/lib/QueryParametersRetriever.ts b/themes/squares/client/src/lib/QueryParametersRetriever.ts
new file mode 100644
index 00000000..a529adb6
--- /dev/null
+++ b/themes/squares/client/src/lib/QueryParametersRetriever.ts
@@ -0,0 +1,12 @@
+
+export class QueryParametersRetriever {
+  static get(name: string, url?: string): string {
+    if (!url) url = window.location.href;
+    name = name.replace(/[\[\]]/g, "\\$&");
+    const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
+      results = regex.exec(url);
+    if (!results) return undefined;
+    if (!results[2]) return "";
+    return decodeURIComponent(results[2].replace(/\+/g, " "));
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/client/src/lib/SafeRedirect.ts b/themes/squares/client/src/lib/SafeRedirect.ts
new file mode 100644
index 00000000..7e7684b8
--- /dev/null
+++ b/themes/squares/client/src/lib/SafeRedirect.ts
@@ -0,0 +1,10 @@
+import { BelongToDomain } from "../../../shared/BelongToDomain";
+
+export function SafeRedirect(url: string, cb: () => void): void {
+  const domain = window.location.hostname.split(".").slice(-2).join(".");
+  if (url.startsWith("/") || BelongToDomain(url, domain)) {
+    window.location.href = url;
+    return;
+  }
+  cb();
+}
\ No newline at end of file
diff --git a/themes/squares/client/src/lib/firstfactor/FirstFactorValidator.ts b/themes/squares/client/src/lib/firstfactor/FirstFactorValidator.ts
new file mode 100644
index 00000000..eaa496fd
--- /dev/null
+++ b/themes/squares/client/src/lib/firstfactor/FirstFactorValidator.ts
@@ -0,0 +1,46 @@
+
+import BluebirdPromise = require("bluebird");
+import Endpoints = require("../../../../shared/api");
+import Constants = require("../../../../shared/constants");
+import Util = require("util");
+import UserMessages = require("../../../../shared/UserMessages");
+
+export function validate(username: string, password: string,
+  keepMeLoggedIn: boolean, redirectUrl: string, $: JQueryStatic)
+  : BluebirdPromise<string> {
+  return new BluebirdPromise<string>(function (resolve, reject) {
+    let url: string;
+    if (redirectUrl != undefined) {
+      const redirectParam = Util.format("%s=%s", Constants.REDIRECT_QUERY_PARAM, redirectUrl);
+      url = Util.format("%s?%s", Endpoints.FIRST_FACTOR_POST, redirectParam);
+    }
+    else {
+      url = Util.format("%s", Endpoints.FIRST_FACTOR_POST);
+    }
+
+    const data: any = {
+      username: username,
+      password: password,
+    };
+
+    if (keepMeLoggedIn) {
+      data.keepMeLoggedIn = "true";
+    }
+
+    $.ajax({
+      method: "POST",
+      url: url,
+      data: data
+    })
+      .done(function (body: any) {
+        if (body && body.error) {
+          reject(new Error(body.error));
+          return;
+        }
+        resolve(body.redirect);
+      })
+      .fail(function (xhr: JQueryXHR, textStatus: string) {
+        reject(new Error(UserMessages.AUTHENTICATION_FAILED));
+      });
+  });
+}
diff --git a/themes/squares/client/src/lib/firstfactor/UISelectors.ts b/themes/squares/client/src/lib/firstfactor/UISelectors.ts
new file mode 100644
index 00000000..0e971b3c
--- /dev/null
+++ b/themes/squares/client/src/lib/firstfactor/UISelectors.ts
@@ -0,0 +1,5 @@
+
+export const USERNAME_FIELD_ID = "#username";
+export const PASSWORD_FIELD_ID = "#password";
+export const SIGN_IN_BUTTON_ID = "#signin";
+export const KEEP_ME_LOGGED_IN_ID = "#keep_me_logged_in";
diff --git a/themes/squares/client/src/lib/firstfactor/index.ts b/themes/squares/client/src/lib/firstfactor/index.ts
new file mode 100644
index 00000000..24affee2
--- /dev/null
+++ b/themes/squares/client/src/lib/firstfactor/index.ts
@@ -0,0 +1,49 @@
+import FirstFactorValidator = require("./FirstFactorValidator");
+import JSLogger = require("js-logger");
+import UISelectors = require("./UISelectors");
+import { Notifier } from "../Notifier";
+import { QueryParametersRetriever } from "../QueryParametersRetriever";
+import Constants = require("../../../../shared/constants");
+import Endpoints = require("../../../../shared/api");
+import UserMessages = require("../../../../shared/UserMessages");
+import { SafeRedirect } from "../SafeRedirect";
+
+export default function (window: Window, $: JQueryStatic,
+  firstFactorValidator: typeof FirstFactorValidator, jslogger: typeof JSLogger) {
+
+  const notifier = new Notifier(".notification", $);
+
+  function onFormSubmitted() {
+    const username: string = $(UISelectors.USERNAME_FIELD_ID).val() as string;
+    const password: string = $(UISelectors.PASSWORD_FIELD_ID).val() as string;
+    const keepMeLoggedIn: boolean = $(UISelectors.KEEP_ME_LOGGED_IN_ID).is(":checked");
+
+    $("form").css("opacity", 0.5);
+    $("input,button").attr("disabled", "true");
+    $(UISelectors.SIGN_IN_BUTTON_ID).text("Please wait...");
+
+    const redirectUrl = QueryParametersRetriever.get(Constants.REDIRECT_QUERY_PARAM);
+    firstFactorValidator.validate(username, password, keepMeLoggedIn, redirectUrl, $)
+      .then(onFirstFactorSuccess, onFirstFactorFailure);
+    return false;
+  }
+
+  function onFirstFactorSuccess(redirectUrl: string) {
+    SafeRedirect(redirectUrl, () => {
+      notifier.error("Cannot redirect to an external domain.");
+    });
+  }
+
+  function onFirstFactorFailure(err: Error) {
+    $("input,button").removeAttr("disabled");
+    $("form").css("opacity", 1);
+    notifier.error(UserMessages.AUTHENTICATION_FAILED);
+    $(UISelectors.PASSWORD_FIELD_ID).select();
+    $(UISelectors.SIGN_IN_BUTTON_ID).text("Sign in");
+  }
+
+  $(window.document).ready(function () {
+    $("form").on("submit", onFormSubmitted);
+  });
+}
+
diff --git a/themes/squares/client/src/lib/reset-password/constants.ts b/themes/squares/client/src/lib/reset-password/constants.ts
new file mode 100644
index 00000000..d48d4e67
--- /dev/null
+++ b/themes/squares/client/src/lib/reset-password/constants.ts
@@ -0,0 +1,2 @@
+
+export const FORM_SELECTOR = ".form-signin";
\ No newline at end of file
diff --git a/themes/squares/client/src/lib/reset-password/reset-password-form.ts b/themes/squares/client/src/lib/reset-password/reset-password-form.ts
new file mode 100644
index 00000000..b94279cd
--- /dev/null
+++ b/themes/squares/client/src/lib/reset-password/reset-password-form.ts
@@ -0,0 +1,57 @@
+import BluebirdPromise = require("bluebird");
+
+import Endpoints = require("../../../../shared/api");
+import UserMessages = require("../../../../shared/UserMessages");
+
+import Constants = require("./constants");
+import { Notifier } from "../Notifier";
+
+export default function (window: Window, $: JQueryStatic) {
+  const notifier = new Notifier(".notification", $);
+
+  function modifyPassword(newPassword: string) {
+    return new BluebirdPromise(function (resolve, reject) {
+      $.post(Endpoints.RESET_PASSWORD_FORM_POST, {
+        password: newPassword,
+      })
+        .done(function (body: any) {
+          if (body && body.error) {
+            reject(new Error(body.error));
+            return;
+          }
+          resolve(body);
+        })
+        .fail(function (xhr, status) {
+          reject(status);
+        });
+    });
+  }
+
+  function onFormSubmitted() {
+    const password1 = $("#password1").val() as string;
+    const password2 = $("#password2").val() as string;
+
+    if (!password1 || !password2) {
+      notifier.warning(UserMessages.MISSING_PASSWORD);
+      return false;
+    }
+
+    if (password1 != password2) {
+      notifier.warning(UserMessages.DIFFERENT_PASSWORDS);
+      return false;
+    }
+
+    modifyPassword(password1)
+      .then(function () {
+        window.location.href = Endpoints.FIRST_FACTOR_GET;
+      })
+      .error(function () {
+        notifier.error(UserMessages.RESET_PASSWORD_FAILED);
+      });
+    return false;
+  }
+
+  $(document).ready(function () {
+    $(Constants.FORM_SELECTOR).on("submit", onFormSubmitted);
+  });
+}
diff --git a/themes/squares/client/src/lib/reset-password/reset-password-request.ts b/themes/squares/client/src/lib/reset-password/reset-password-request.ts
new file mode 100644
index 00000000..846226d7
--- /dev/null
+++ b/themes/squares/client/src/lib/reset-password/reset-password-request.ts
@@ -0,0 +1,56 @@
+
+import BluebirdPromise = require("bluebird");
+
+import Endpoints = require("../../../../shared/api");
+import UserMessages = require("../../../../shared/UserMessages");
+import Constants = require("./constants");
+import jslogger = require("js-logger");
+import { Notifier } from "../Notifier";
+
+export default function (window: Window, $: JQueryStatic) {
+  const notifier = new Notifier(".notification", $);
+
+  function requestPasswordReset(username: string) {
+    return new BluebirdPromise(function (resolve, reject) {
+      $.get(Endpoints.RESET_PASSWORD_IDENTITY_START_GET, {
+        userid: username,
+      })
+        .done(function (body: any) {
+          if (body && body.error) {
+            reject(new Error(body.error));
+            return;
+          }
+          resolve();
+        })
+        .fail(function (xhr: JQueryXHR, textStatus: string) {
+          reject(new Error(textStatus));
+        });
+    });
+  }
+
+  function onFormSubmitted() {
+    const username = $("#username").val() as string;
+
+    if (!username) {
+      notifier.warning(UserMessages.MISSING_USERNAME);
+      return;
+    }
+
+    requestPasswordReset(username)
+      .then(function () {
+        notifier.success(UserMessages.MAIL_SENT);
+        setTimeout(function () {
+          window.location.replace(Endpoints.FIRST_FACTOR_GET);
+        }, 1000);
+      })
+      .error(function () {
+        notifier.error(UserMessages.MAIL_NOT_SENT);
+      });
+    return false;
+  }
+
+  $(document).ready(function () {
+    $(Constants.FORM_SELECTOR).on("submit", onFormSubmitted);
+  });
+}
+
diff --git a/themes/squares/client/src/lib/secondfactor/TOTPValidator.ts b/themes/squares/client/src/lib/secondfactor/TOTPValidator.ts
new file mode 100644
index 00000000..5394139a
--- /dev/null
+++ b/themes/squares/client/src/lib/secondfactor/TOTPValidator.ts
@@ -0,0 +1,28 @@
+
+import BluebirdPromise = require("bluebird");
+import Endpoints = require("../../../../shared/api");
+import { RedirectionMessage } from "../../../../shared/RedirectionMessage";
+import { ErrorMessage } from "../../../../shared/ErrorMessage";
+
+export function validate(token: string, $: JQueryStatic): BluebirdPromise<string> {
+  return new BluebirdPromise<string>(function (resolve, reject) {
+    $.ajax({
+      url: Endpoints.SECOND_FACTOR_TOTP_POST,
+      data: {
+        token: token,
+      },
+      method: "POST",
+      dataType: "json"
+    } as JQueryAjaxSettings)
+      .done(function (body: RedirectionMessage | ErrorMessage) {
+        if (body && "error" in body) {
+          reject(new Error((body as ErrorMessage).error));
+          return;
+        }
+        resolve((body as RedirectionMessage).redirect);
+      })
+      .fail(function (xhr: JQueryXHR, textStatus: string) {
+        reject(new Error(textStatus));
+      });
+  });
+}
\ No newline at end of file
diff --git a/themes/squares/client/src/lib/secondfactor/U2FValidator.ts b/themes/squares/client/src/lib/secondfactor/U2FValidator.ts
new file mode 100644
index 00000000..5812922f
--- /dev/null
+++ b/themes/squares/client/src/lib/secondfactor/U2FValidator.ts
@@ -0,0 +1,42 @@
+import U2f = require("u2f");
+import U2fApi from "u2f-api";
+import BluebirdPromise = require("bluebird");
+import Endpoints = require("../../../../shared/api");
+import UserMessages = require("../../../../shared/UserMessages");
+import { INotifier } from "../INotifier";
+import { RedirectionMessage } from "../../../../shared/RedirectionMessage";
+import { ErrorMessage } from "../../../../shared/ErrorMessage";
+import GetPromised from "../GetPromised";
+
+function finishU2fAuthentication(responseData: U2fApi.SignResponse,
+  $: JQueryStatic): BluebirdPromise<string> {
+  return new BluebirdPromise<string>(function (resolve, reject) {
+    $.ajax({
+      url: Endpoints.SECOND_FACTOR_U2F_SIGN_POST,
+      data: responseData,
+      method: "POST",
+      dataType: "json"
+    } as JQueryAjaxSettings)
+      .done(function (body: RedirectionMessage | ErrorMessage) {
+        if (body && "error" in body) {
+          reject(new Error((body as ErrorMessage).error));
+          return;
+        }
+        resolve((body as RedirectionMessage).redirect);
+      })
+      .fail(function (xhr: JQueryXHR, textStatus: string) {
+        reject(new Error(textStatus));
+      });
+  });
+}
+
+export function validate($: JQueryStatic): BluebirdPromise<string> {
+  return GetPromised($, Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, {},
+    undefined, "json")
+    .then(function (signRequest: U2f.Request) {
+      return U2fApi.sign(signRequest, 60);
+    })
+    .then(function (signResponse: U2fApi.SignResponse) {
+      return finishU2fAuthentication(signResponse, $);
+    });
+}
diff --git a/themes/squares/client/src/lib/secondfactor/constants.ts b/themes/squares/client/src/lib/secondfactor/constants.ts
new file mode 100644
index 00000000..50bba757
--- /dev/null
+++ b/themes/squares/client/src/lib/secondfactor/constants.ts
@@ -0,0 +1,3 @@
+
+export const TOTP_FORM_SELECTOR = ".form-signin.totp";
+export const TOTP_TOKEN_SELECTOR = ".form-signin #token";
diff --git a/themes/squares/client/src/lib/secondfactor/index.ts b/themes/squares/client/src/lib/secondfactor/index.ts
new file mode 100644
index 00000000..279723dc
--- /dev/null
+++ b/themes/squares/client/src/lib/secondfactor/index.ts
@@ -0,0 +1,59 @@
+import TOTPValidator = require("./TOTPValidator");
+import U2FValidator = require("./U2FValidator");
+import ClientConstants = require("./constants");
+import { Notifier } from "../Notifier";
+import { QueryParametersRetriever } from "../QueryParametersRetriever";
+import UserMessages = require("../../../../shared/UserMessages");
+import SharedConstants = require("../../../../shared/constants");
+import { SafeRedirect } from "../SafeRedirect";
+
+export default function (window: Window, $: JQueryStatic) {
+  const notifier = new Notifier(".notification", $);
+
+  function onAuthenticationSuccess(serverRedirectUrl: string) {
+    const queryRedirectUrl = QueryParametersRetriever.get(SharedConstants.REDIRECT_QUERY_PARAM);
+    if (queryRedirectUrl) {
+      SafeRedirect(queryRedirectUrl, () => {
+        notifier.error(UserMessages.CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN);
+      });
+    } else if (serverRedirectUrl) {
+      SafeRedirect(serverRedirectUrl, () => {
+        notifier.error(UserMessages.CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN);
+      });
+    } else {
+      notifier.success(UserMessages.AUTHENTICATION_SUCCEEDED);
+    }
+  }
+
+  function onSecondFactorTotpSuccess(redirectUrl: string) {
+    onAuthenticationSuccess(redirectUrl);
+  }
+
+  function onSecondFactorTotpFailure(err: Error) {
+    notifier.error(UserMessages.AUTHENTICATION_TOTP_FAILED);
+  }
+
+  function onU2fAuthenticationSuccess(redirectUrl: string) {
+    onAuthenticationSuccess(redirectUrl);
+  }
+
+  function onU2fAuthenticationFailure() {
+    // TODO(clems4ever): we should not display this error message until a device
+    //                   is registered.
+    // notifier.error(UserMessages.AUTHENTICATION_U2F_FAILED);
+  }
+
+  function onTOTPFormSubmitted(): boolean {
+    const token = $(ClientConstants.TOTP_TOKEN_SELECTOR).val() as string;
+    TOTPValidator.validate(token, $)
+      .then(onSecondFactorTotpSuccess)
+      .catch(onSecondFactorTotpFailure);
+    return false;
+  }
+
+  $(window.document).ready(function () {
+    $(ClientConstants.TOTP_FORM_SELECTOR).on("submit", onTOTPFormSubmitted);
+    U2FValidator.validate($)
+      .then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure);
+  });
+}
\ No newline at end of file
diff --git a/themes/squares/client/src/lib/totp-register/totp-register.ts b/themes/squares/client/src/lib/totp-register/totp-register.ts
new file mode 100644
index 00000000..6a9aa7ee
--- /dev/null
+++ b/themes/squares/client/src/lib/totp-register/totp-register.ts
@@ -0,0 +1,11 @@
+
+import jslogger = require("js-logger");
+import UISelector = require("./ui-selector");
+
+export default function(window: Window, $: JQueryStatic) {
+  jslogger.debug("Creating QRCode from OTPAuth url");
+  const qrcode = $(UISelector.QRCODE_ID_SELECTOR);
+  const val = qrcode.text();
+  qrcode.empty();
+  new (window as any).QRCode(qrcode.get(0), val);
+}
diff --git a/themes/squares/client/src/lib/totp-register/ui-selector.ts b/themes/squares/client/src/lib/totp-register/ui-selector.ts
new file mode 100644
index 00000000..9d43fabe
--- /dev/null
+++ b/themes/squares/client/src/lib/totp-register/ui-selector.ts
@@ -0,0 +1,2 @@
+
+export const QRCODE_ID_SELECTOR = "#qrcode";
\ No newline at end of file
diff --git a/themes/squares/client/src/lib/u2f-register/u2f-register.ts b/themes/squares/client/src/lib/u2f-register/u2f-register.ts
new file mode 100644
index 00000000..abf40ee0
--- /dev/null
+++ b/themes/squares/client/src/lib/u2f-register/u2f-register.ts
@@ -0,0 +1,56 @@
+
+import BluebirdPromise = require("bluebird");
+import U2f = require("u2f");
+import * as U2fApi from "u2f-api";
+import { Notifier } from "../Notifier";
+import GetPromised from "../GetPromised";
+import Endpoints = require("../../../../shared/api");
+import UserMessages = require("../../../../shared/UserMessages");
+import { RedirectionMessage } from "../../../../shared/RedirectionMessage";
+import { ErrorMessage } from "../../../../shared/ErrorMessage";
+import { SafeRedirect } from "../SafeRedirect";
+
+export default function (window: Window, $: JQueryStatic) {
+  const notifier = new Notifier(".notification", $);
+
+  function checkRegistration(regResponse: U2fApi.RegisterResponse): BluebirdPromise<string> {
+    return new BluebirdPromise<string>(function (resolve, reject) {
+      $.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, regResponse, undefined, "json")
+        .done((body: RedirectionMessage | ErrorMessage) => {
+          if (body && "error" in body) {
+            reject(new Error((body as ErrorMessage).error));
+            return;
+          }
+          resolve((body as RedirectionMessage).redirect);
+        })
+        .fail((xhr, status) => {
+          reject(new Error("Failed to register device."));
+        });
+    });
+  }
+
+  function requestRegistration(): BluebirdPromise<string> {
+    return GetPromised($, Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, {},
+      undefined, "json")
+      .then((registrationRequest: U2f.Request) => {
+        return U2fApi.register(registrationRequest, [], 60);
+      })
+      .then((res) => checkRegistration(res));
+  }
+
+  function onRegisterFailure(err: Error) {
+    notifier.error(UserMessages.REGISTRATION_U2F_FAILED);
+  }
+
+  $(document).ready(function () {
+    requestRegistration()
+      .then((redirectionUrl: string) => {
+        SafeRedirect(redirectionUrl, () => {
+          notifier.error(UserMessages.CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN);
+        });
+      })
+      .catch((err) => {
+        onRegisterFailure(err);
+      });
+  });
+}
diff --git a/themes/squares/client/src/thirdparties/qrcode.min.js b/themes/squares/client/src/thirdparties/qrcode.min.js
new file mode 100644
index 00000000..993e88f3
--- /dev/null
+++ b/themes/squares/client/src/thirdparties/qrcode.min.js
@@ -0,0 +1 @@
+var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c<a.length&&0==a[c];)c++;this.num=new Array(a.length-c+b);for(var d=0;d<a.length-c;d++)this.num[d]=a[d+c]}function j(a,b){this.totalCount=a,this.dataCount=b}function k(){this.buffer=[],this.length=0}function m(){return"undefined"!=typeof CanvasRenderingContext2D}function n(){var a=!1,b=navigator.userAgent;return/android/i.test(b)&&(a=!0,aMat=b.toString().match(/android ([0-9]\.[0-9])/i),aMat&&aMat[1]&&(a=parseFloat(aMat[1]))),a}function r(a,b){for(var c=1,e=s(a),f=0,g=l.length;g>=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=new Array(this.moduleCount);for(var e=0;e<this.moduleCount;e++)this.modules[d][e]=null}this.setupPositionProbePattern(0,0),this.setupPositionProbePattern(this.moduleCount-7,0),this.setupPositionProbePattern(0,this.moduleCount-7),this.setupPositionAdjustPattern(),this.setupTimingPattern(),this.setupTypeInfo(a,c),this.typeNumber>=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f<this.modules.length;f++)for(var g=f*e,h=0;h<this.modules[f].length;h++){var i=h*e,j=this.modules[f][h];j&&(d.beginFill(0,100),d.moveTo(i,g),d.lineTo(i+e,g),d.lineTo(i+e,g+e),d.lineTo(i,g+e),d.endFill())}return d},setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(var b=8;b<this.moduleCount-8;b++)null==this.modules[6][b]&&(this.modules[6][b]=0==b%2)},setupPositionAdjustPattern:function(){for(var a=f.getPatternPosition(this.typeNumber),b=0;b<a.length;b++)for(var c=0;c<a.length;c++){var d=a[b],e=a[c];if(null==this.modules[d][e])for(var g=-2;2>=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g<a.length&&(j=1==(1&a[g]>>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h<d.length;h++){var i=d[h];g.put(i.mode,4),g.put(i.getLength(),f.getLengthInBits(i.mode,a)),i.write(g)}for(var l=0,h=0;h<e.length;h++)l+=e[h].dataCount;if(g.getLengthInBits()>8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j<b.length;j++){var k=b[j].dataCount,l=b[j].totalCount-k;d=Math.max(d,k),e=Math.max(e,l),g[j]=new Array(k);for(var m=0;m<g[j].length;m++)g[j][m]=255&a.buffer[m+c];c+=k;var n=f.getErrorCorrectPolynomial(l),o=new i(g[j],n.getLength()-1),p=o.mod(n);h[j]=new Array(n.getLength()-1);for(var m=0;m<h[j].length;m++){var q=m+p.getLength()-h[j].length;h[j][m]=q>=0?p.get(q):0}}for(var r=0,m=0;m<b.length;m++)r+=b[m].totalCount;for(var s=new Array(r),t=0,m=0;d>m;m++)for(var j=0;j<b.length;j++)m<g[j].length&&(s[t++]=g[j][m]);for(var m=0;e>m;m++)for(var j=0;j<b.length;j++)m<h[j].length&&(s[t++]=h[j][m]);return s};for(var c={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},d={L:1,M:0,Q:3,H:2},e={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},f={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(a){for(var b=a<<10;f.getBCHDigit(b)-f.getBCHDigit(f.G15)>=0;)b^=f.G15<<f.getBCHDigit(b)-f.getBCHDigit(f.G15);return(a<<10|b)^f.G15_MASK},getBCHTypeNumber:function(a){for(var b=a<<12;f.getBCHDigit(b)-f.getBCHDigit(f.G18)>=0;)b^=f.G18<<f.getBCHDigit(b)-f.getBCHDigit(f.G18);return a<<12|b},getBCHDigit:function(a){for(var b=0;0!=a;)b++,a>>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<<h;for(var h=8;256>h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;c<this.getLength();c++)for(var d=0;d<a.getLength();d++)b[c+d]^=g.gexp(g.glog(this.get(c))+g.glog(a.get(d)));return new i(b,0)},mod:function(a){if(this.getLength()-a.getLength()<0)return this;for(var b=g.glog(this.get(0))-g.glog(a.get(0)),c=new Array(this.getLength()),d=0;d<this.getLength();d++)c[d]=this.get(d);for(var d=0;d<a.getLength();d++)c[d]^=g.gexp(g.glog(a.get(d))+b);return new i(c,0).mod(a)}},j.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],j.getRSBlocks=function(a,b){var c=j.getRsBlockTable(a,b);if(void 0==c)throw new Error("bad rs block @ typeNumber:"+a+"/errorCorrectLevel:"+b);for(var d=c.length/3,e=[],f=0;d>f;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=['<table style="border:0;border-collapse:collapse;">'],h=0;d>h;h++){g.push("<tr>");for(var i=0;d>i;i++)g.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:'+e+"px;height:"+f+"px;background-color:"+(a.isDark(h,i)?b.colorDark:b.colorLight)+';"></td>');g.push("</tr>")}g.push("</table>"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}();
\ No newline at end of file
diff --git a/themes/squares/client/src/thirdparties/u2f-api.js b/themes/squares/client/src/thirdparties/u2f-api.js
new file mode 100644
index 00000000..8c7801e3
--- /dev/null
+++ b/themes/squares/client/src/thirdparties/u2f-api.js
@@ -0,0 +1,749 @@
+//Copyright 2014-2015 Google Inc. All rights reserved.
+
+//Use of this source code is governed by a BSD-style
+//license that can be found in the LICENSE file or at
+//https://developers.google.com/open-source/licenses/bsd
+
+/**
+ * @fileoverview The U2F api.
+ */
+'use strict';
+
+
+/**
+ * Namespace for the U2F api.
+ * @type {Object}
+ */
+var u2f = u2f || {};
+
+/**
+ * FIDO U2F Javascript API Version
+ * @number
+ */
+var js_api_version;
+
+/**
+ * The U2F extension id
+ * @const {string}
+ */
+// The Chrome packaged app extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the package Chrome app and does not require installing the U2F Chrome extension.
+ u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
+// The U2F Chrome extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the U2F Chrome extension to authenticate.
+// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
+
+
+/**
+ * Message types for messsages to/from the extension
+ * @const
+ * @enum {string}
+ */
+u2f.MessageTypes = {
+    'U2F_REGISTER_REQUEST': 'u2f_register_request',
+    'U2F_REGISTER_RESPONSE': 'u2f_register_response',
+    'U2F_SIGN_REQUEST': 'u2f_sign_request',
+    'U2F_SIGN_RESPONSE': 'u2f_sign_response',
+    'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
+    'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
+};
+
+
+/**
+ * Response status codes
+ * @const
+ * @enum {number}
+ */
+u2f.ErrorCodes = {
+    'OK': 0,
+    'OTHER_ERROR': 1,
+    'BAD_REQUEST': 2,
+    'CONFIGURATION_UNSUPPORTED': 3,
+    'DEVICE_INELIGIBLE': 4,
+    'TIMEOUT': 5
+};
+
+
+/**
+ * A message for registration requests
+ * @typedef {{
+ *   type: u2f.MessageTypes,
+ *   appId: ?string,
+ *   timeoutSeconds: ?number,
+ *   requestId: ?number
+ * }}
+ */
+u2f.U2fRequest;
+
+
+/**
+ * A message for registration responses
+ * @typedef {{
+ *   type: u2f.MessageTypes,
+ *   responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
+ *   requestId: ?number
+ * }}
+ */
+u2f.U2fResponse;
+
+
+/**
+ * An error object for responses
+ * @typedef {{
+ *   errorCode: u2f.ErrorCodes,
+ *   errorMessage: ?string
+ * }}
+ */
+u2f.Error;
+
+/**
+ * Data object for a single sign request.
+ * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
+ */
+u2f.Transport;
+
+
+/**
+ * Data object for a single sign request.
+ * @typedef {Array<u2f.Transport>}
+ */
+u2f.Transports;
+
+/**
+ * Data object for a single sign request.
+ * @typedef {{
+ *   version: string,
+ *   challenge: string,
+ *   keyHandle: string,
+ *   appId: string
+ * }}
+ */
+u2f.SignRequest;
+
+
+/**
+ * Data object for a sign response.
+ * @typedef {{
+ *   keyHandle: string,
+ *   signatureData: string,
+ *   clientData: string
+ * }}
+ */
+u2f.SignResponse;
+
+
+/**
+ * Data object for a registration request.
+ * @typedef {{
+ *   version: string,
+ *   challenge: string
+ * }}
+ */
+u2f.RegisterRequest;
+
+
+/**
+ * Data object for a registration response.
+ * @typedef {{
+ *   version: string,
+ *   keyHandle: string,
+ *   transports: Transports,
+ *   appId: string
+ * }}
+ */
+u2f.RegisterResponse;
+
+
+/**
+ * Data object for a registered key.
+ * @typedef {{
+ *   version: string,
+ *   keyHandle: string,
+ *   transports: ?Transports,
+ *   appId: ?string
+ * }}
+ */
+u2f.RegisteredKey;
+
+
+/**
+ * Data object for a get API register response.
+ * @typedef {{
+ *   js_api_version: number
+ * }}
+ */
+u2f.GetJsApiVersionResponse;
+
+
+//Low level MessagePort API support
+
+/**
+ * Sets up a MessagePort to the U2F extension using the
+ * available mechanisms.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ */
+u2f.getMessagePort = function(callback) {
+  if (typeof chrome != 'undefined' && chrome.runtime) {
+    // The actual message here does not matter, but we need to get a reply
+    // for the callback to run. Thus, send an empty signature request
+    // in order to get a failure response.
+    var msg = {
+        type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+        signRequests: []
+    };
+    chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
+      if (!chrome.runtime.lastError) {
+        // We are on a whitelisted origin and can talk directly
+        // with the extension.
+        u2f.getChromeRuntimePort_(callback);
+      } else {
+        // chrome.runtime was available, but we couldn't message
+        // the extension directly, use iframe
+        u2f.getIframePort_(callback);
+      }
+    });
+  } else if (u2f.isAndroidChrome_()) {
+    u2f.getAuthenticatorPort_(callback);
+  } else if (u2f.isIosChrome_()) {
+    u2f.getIosPort_(callback);
+  } else {
+    // chrome.runtime was not available at all, which is normal
+    // when this origin doesn't have access to any extensions.
+    u2f.getIframePort_(callback);
+  }
+};
+
+/**
+ * Detect chrome running on android based on the browser's useragent.
+ * @private
+ */
+u2f.isAndroidChrome_ = function() {
+  var userAgent = navigator.userAgent;
+  return userAgent.indexOf('Chrome') != -1 &&
+  userAgent.indexOf('Android') != -1;
+};
+
+/**
+ * Detect chrome running on iOS based on the browser's platform.
+ * @private
+ */
+u2f.isIosChrome_ = function() {
+  return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
+};
+
+/**
+ * Connects directly to the extension via chrome.runtime.connect.
+ * @param {function(u2f.WrappedChromeRuntimePort_)} callback
+ * @private
+ */
+u2f.getChromeRuntimePort_ = function(callback) {
+  var port = chrome.runtime.connect(u2f.EXTENSION_ID,
+      {'includeTlsChannelId': true});
+  setTimeout(function() {
+    callback(new u2f.WrappedChromeRuntimePort_(port));
+  }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the Authenticator app.
+ * @param {function(u2f.WrappedAuthenticatorPort_)} callback
+ * @private
+ */
+u2f.getAuthenticatorPort_ = function(callback) {
+  setTimeout(function() {
+    callback(new u2f.WrappedAuthenticatorPort_());
+  }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the iOS client app.
+ * @param {function(u2f.WrappedIosPort_)} callback
+ * @private
+ */
+u2f.getIosPort_ = function(callback) {
+  setTimeout(function() {
+    callback(new u2f.WrappedIosPort_());
+  }, 0);
+};
+
+/**
+ * A wrapper for chrome.runtime.Port that is compatible with MessagePort.
+ * @param {Port} port
+ * @constructor
+ * @private
+ */
+u2f.WrappedChromeRuntimePort_ = function(port) {
+  this.port_ = port;
+};
+
+/**
+ * Format and return a sign request compliant with the JS API version supported by the extension.
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.formatSignRequest_ =
+  function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
+  if (js_api_version === undefined || js_api_version < 1.1) {
+    // Adapt request to the 1.0 JS API
+    var signRequests = [];
+    for (var i = 0; i < registeredKeys.length; i++) {
+      signRequests[i] = {
+          version: registeredKeys[i].version,
+          challenge: challenge,
+          keyHandle: registeredKeys[i].keyHandle,
+          appId: appId
+      };
+    }
+    return {
+      type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+      signRequests: signRequests,
+      timeoutSeconds: timeoutSeconds,
+      requestId: reqId
+    };
+  }
+  // JS 1.1 API
+  return {
+    type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+    appId: appId,
+    challenge: challenge,
+    registeredKeys: registeredKeys,
+    timeoutSeconds: timeoutSeconds,
+    requestId: reqId
+  };
+};
+
+/**
+ * Format and return a register request compliant with the JS API version supported by the extension..
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {Array<u2f.RegisterRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.formatRegisterRequest_ =
+  function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
+  if (js_api_version === undefined || js_api_version < 1.1) {
+    // Adapt request to the 1.0 JS API
+    for (var i = 0; i < registerRequests.length; i++) {
+      registerRequests[i].appId = appId;
+    }
+    var signRequests = [];
+    for (var i = 0; i < registeredKeys.length; i++) {
+      signRequests[i] = {
+          version: registeredKeys[i].version,
+          challenge: registerRequests[0],
+          keyHandle: registeredKeys[i].keyHandle,
+          appId: appId
+      };
+    }
+    return {
+      type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+      signRequests: signRequests,
+      registerRequests: registerRequests,
+      timeoutSeconds: timeoutSeconds,
+      requestId: reqId
+    };
+  }
+  // JS 1.1 API
+  return {
+    type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+    appId: appId,
+    registerRequests: registerRequests,
+    registeredKeys: registeredKeys,
+    timeoutSeconds: timeoutSeconds,
+    requestId: reqId
+  };
+};
+
+
+/**
+ * Posts a message on the underlying channel.
+ * @param {Object} message
+ */
+u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
+  this.port_.postMessage(message);
+};
+
+
+/**
+ * Emulates the HTML 5 addEventListener interface. Works only for the
+ * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
+    function(eventName, handler) {
+  var name = eventName.toLowerCase();
+  if (name == 'message' || name == 'onmessage') {
+    this.port_.onMessage.addListener(function(message) {
+      // Emulate a minimal MessageEvent object
+      handler({'data': message});
+    });
+  } else {
+    console.error('WrappedChromeRuntimePort only supports onMessage');
+  }
+};
+
+/**
+ * Wrap the Authenticator app with a MessagePort interface.
+ * @constructor
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_ = function() {
+  this.requestId_ = -1;
+  this.requestObject_ = null;
+}
+
+/**
+ * Launch the Authenticator intent.
+ * @param {Object} message
+ */
+u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
+  var intentUrl =
+    u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
+    ';S.request=' + encodeURIComponent(JSON.stringify(message)) +
+    ';end';
+  document.location = intentUrl;
+};
+
+/**
+ * Tells what type of port this is.
+ * @return {String} port type
+ */
+u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
+  return "WrappedAuthenticatorPort_";
+};
+
+
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) {
+  var name = eventName.toLowerCase();
+  if (name == 'message') {
+    var self = this;
+    /* Register a callback to that executes when
+     * chrome injects the response. */
+    window.addEventListener(
+        'message', self.onRequestUpdate_.bind(self, handler), false);
+  } else {
+    console.error('WrappedAuthenticatorPort only supports message');
+  }
+};
+
+/**
+ * Callback invoked  when a response is received from the Authenticator.
+ * @param function({data: Object}) callback
+ * @param {Object} message message Object
+ */
+u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
+    function(callback, message) {
+  var messageObject = JSON.parse(message.data);
+  var intentUrl = messageObject['intentURL'];
+
+  var errorCode = messageObject['errorCode'];
+  var responseObject = null;
+  if (messageObject.hasOwnProperty('data')) {
+    responseObject = /** @type {Object} */ (
+        JSON.parse(messageObject['data']));
+  }
+
+  callback({'data': responseObject});
+};
+
+/**
+ * Base URL for intents to Authenticator.
+ * @const
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
+  'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
+
+/**
+ * Wrap the iOS client app with a MessagePort interface.
+ * @constructor
+ * @private
+ */
+u2f.WrappedIosPort_ = function() {};
+
+/**
+ * Launch the iOS client app request
+ * @param {Object} message
+ */
+u2f.WrappedIosPort_.prototype.postMessage = function(message) {
+  var str = JSON.stringify(message);
+  var url = "u2f://auth?" + encodeURI(str);
+  location.replace(url);
+};
+
+/**
+ * Tells what type of port this is.
+ * @return {String} port type
+ */
+u2f.WrappedIosPort_.prototype.getPortType = function() {
+  return "WrappedIosPort_";
+};
+
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
+  var name = eventName.toLowerCase();
+  if (name !== 'message') {
+    console.error('WrappedIosPort only supports message');
+  }
+};
+
+/**
+ * Sets up an embedded trampoline iframe, sourced from the extension.
+ * @param {function(MessagePort)} callback
+ * @private
+ */
+u2f.getIframePort_ = function(callback) {
+  // Create the iframe
+  var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
+  var iframe = document.createElement('iframe');
+  iframe.src = iframeOrigin + '/u2f-comms.html';
+  iframe.setAttribute('style', 'display:none');
+  document.body.appendChild(iframe);
+
+  var channel = new MessageChannel();
+  var ready = function(message) {
+    if (message.data == 'ready') {
+      channel.port1.removeEventListener('message', ready);
+      callback(channel.port1);
+    } else {
+      console.error('First event on iframe port was not "ready"');
+    }
+  };
+  channel.port1.addEventListener('message', ready);
+  channel.port1.start();
+
+  iframe.addEventListener('load', function() {
+    // Deliver the port to the iframe and initialize
+    iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
+  });
+};
+
+
+//High-level JS API
+
+/**
+ * Default extension response timeout in seconds.
+ * @const
+ */
+u2f.EXTENSION_TIMEOUT_SEC = 30;
+
+/**
+ * A singleton instance for a MessagePort to the extension.
+ * @type {MessagePort|u2f.WrappedChromeRuntimePort_}
+ * @private
+ */
+u2f.port_ = null;
+
+/**
+ * Callbacks waiting for a port
+ * @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
+ * @private
+ */
+u2f.waitingForPort_ = [];
+
+/**
+ * A counter for requestIds.
+ * @type {number}
+ * @private
+ */
+u2f.reqCounter_ = 0;
+
+/**
+ * A map from requestIds to client callbacks
+ * @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
+ *                       |function((u2f.Error|u2f.SignResponse)))>}
+ * @private
+ */
+u2f.callbackMap_ = {};
+
+/**
+ * Creates or retrieves the MessagePort singleton to use.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ * @private
+ */
+u2f.getPortSingleton_ = function(callback) {
+  if (u2f.port_) {
+    callback(u2f.port_);
+  } else {
+    if (u2f.waitingForPort_.length == 0) {
+      u2f.getMessagePort(function(port) {
+        u2f.port_ = port;
+        u2f.port_.addEventListener('message',
+            /** @type {function(Event)} */ (u2f.responseHandler_));
+
+        // Careful, here be async callbacks. Maybe.
+        while (u2f.waitingForPort_.length)
+          u2f.waitingForPort_.shift()(u2f.port_);
+      });
+    }
+    u2f.waitingForPort_.push(callback);
+  }
+};
+
+/**
+ * Handles response messages from the extension.
+ * @param {MessageEvent.<u2f.Response>} message
+ * @private
+ */
+u2f.responseHandler_ = function(message) {
+  var response = message.data;
+  var reqId = response['requestId'];
+  if (!reqId || !u2f.callbackMap_[reqId]) {
+    console.error('Unknown or missing requestId in response.');
+    return;
+  }
+  var cb = u2f.callbackMap_[reqId];
+  delete u2f.callbackMap_[reqId];
+  cb(response['responseData']);
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the sign request.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+  if (js_api_version === undefined) {
+    // Send a message to get the extension to JS API version, then send the actual sign request.
+    u2f.getApiVersion(
+        function (response) {
+          js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
+          console.log("Extension JS API Version: ", js_api_version);
+          u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+        });
+  } else {
+    // We know the JS API version. Send the actual sign request in the supported API version.
+    u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+  }
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+  u2f.getPortSingleton_(function(port) {
+    var reqId = ++u2f.reqCounter_;
+    u2f.callbackMap_[reqId] = callback;
+    var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+        opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+    var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
+    port.postMessage(req);
+  });
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the register request.
+ * @param {string=} appId
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+  if (js_api_version === undefined) {
+    // Send a message to get the extension to JS API version, then send the actual register request.
+    u2f.getApiVersion(
+        function (response) {
+          js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version'];
+          console.log("Extension JS API Version: ", js_api_version);
+          u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+              callback, opt_timeoutSeconds);
+        });
+  } else {
+    // We know the JS API version. Send the actual register request in the supported API version.
+    u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+        callback, opt_timeoutSeconds);
+  }
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * @param {string=} appId
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+  u2f.getPortSingleton_(function(port) {
+    var reqId = ++u2f.reqCounter_;
+    u2f.callbackMap_[reqId] = callback;
+    var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+        opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+    var req = u2f.formatRegisterRequest_(
+        appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
+    port.postMessage(req);
+  });
+};
+
+
+/**
+ * Dispatches a message to the extension to find out the supported
+ * JS API version.
+ * If the user is on a mobile phone and is thus using Google Authenticator instead
+ * of the Chrome extension, don't send the request and simply return 0.
+ * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+   // If we are using Android Google Authenticator or iOS client app,
+   // do not fire an intent to ask which JS API version to use.
+   if (port.getPortType) {
+     var apiVersion;
+     switch (port.getPortType()) {
+       case 'WrappedIosPort_':
+       case 'WrappedAuthenticatorPort_':
+         apiVersion = 1.1;
+         break;
+
+       default:
+         apiVersion = 0;
+         break;
+     }
+     callback({ 'js_api_version': apiVersion });
+     return;
+   }
+    var reqId = ++u2f.reqCounter_;
+    u2f.callbackMap_[reqId] = callback;
+    var req = {
+      type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
+      timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
+          opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
+      requestId: reqId
+    };
+    port.postMessage(req);
+  });
+};
+
diff --git a/themes/squares/client/test/Notifier.test.ts b/themes/squares/client/test/Notifier.test.ts
new file mode 100644
index 00000000..70bfea14
--- /dev/null
+++ b/themes/squares/client/test/Notifier.test.ts
@@ -0,0 +1,71 @@
+
+import Assert = require("assert");
+import Sinon = require("sinon");
+import JQueryMock = require("./mocks/jquery");
+
+import { Notifier } from "../src/lib/Notifier";
+
+describe("test notifier", function() {
+  const SELECTOR = "dummy-selector";
+  const MESSAGE = "This is a message";
+  let jqueryMock: { jquery: JQueryMock.JQueryMock, element: JQueryMock.JQueryElementsMock };
+  let clock: any;
+
+  beforeEach(function() {
+    jqueryMock = JQueryMock.JQueryMock();
+    clock = Sinon.useFakeTimers();
+  });
+
+  afterEach(function() {
+    clock.restore();
+  });
+
+  function should_fade_in_and_out_on_notification(notificationType: string): void {
+    const delayReturn = {
+      fadeOut: Sinon.stub()
+    };
+
+    jqueryMock.element.fadeIn.yields();
+
+    function onFadedInCallback() {
+      Assert(jqueryMock.element.fadeIn.calledOnce);
+      Assert(jqueryMock.element.addClass.calledWith(notificationType));
+      Assert(!jqueryMock.element.removeClass.calledWith(notificationType));
+      clock.tick(10 * 1000);
+    }
+
+    function onFadedOutCallback() {
+      Assert(jqueryMock.element.removeClass.calledWith(notificationType));
+      Assert(jqueryMock.element.fadeOut.calledOnce);
+    }
+
+    const notifier = new Notifier(SELECTOR, jqueryMock.jquery as any);
+
+    // Call the method by its name... Bad but allows code reuse.
+    (notifier as any)[notificationType](MESSAGE, {
+      onFadedIn: onFadedInCallback,
+      onFadedOut: onFadedOutCallback
+    });
+
+    clock.tick(510);
+
+    Assert(jqueryMock.element.fadeIn.calledOnce);
+  }
+
+
+  it("should fade in and fade out an error message", function() {
+    should_fade_in_and_out_on_notification("error");
+  });
+
+  it("should fade in and fade out an info message", function() {
+    should_fade_in_and_out_on_notification("info");
+  });
+
+  it("should fade in and fade out an warning message", function() {
+    should_fade_in_and_out_on_notification("warning");
+  });
+
+  it("should fade in and fade out an success message", function() {
+    should_fade_in_and_out_on_notification("success");
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/client/test/firstfactor/FirstFactorValidator.test.ts b/themes/squares/client/test/firstfactor/FirstFactorValidator.test.ts
new file mode 100644
index 00000000..ac835327
--- /dev/null
+++ b/themes/squares/client/test/firstfactor/FirstFactorValidator.test.ts
@@ -0,0 +1,44 @@
+
+import FirstFactorValidator = require("../../src/lib/firstfactor/FirstFactorValidator");
+import JQueryMock = require("../mocks/jquery");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+
+describe("test FirstFactorValidator", function () {
+    it("should validate first factor successfully", () => {
+        const postPromise = JQueryMock.JQueryDeferredMock();
+        postPromise.done.yields({ redirect: "http://redirect" });
+        postPromise.done.returns(postPromise);
+
+        const jqueryMock = JQueryMock.JQueryMock();
+        jqueryMock.jquery.ajax.returns(postPromise);
+
+        return FirstFactorValidator.validate("username", "password", "http://redirect", jqueryMock.jquery as any);
+    });
+
+    function should_fail_first_factor_validation(errorMessage: string) {
+        const xhr = {
+            status: 401
+        };
+        const postPromise = JQueryMock.JQueryDeferredMock();
+        postPromise.fail.yields(xhr, errorMessage);
+        postPromise.done.returns(postPromise);
+
+        const jqueryMock = JQueryMock.JQueryMock();
+        jqueryMock.jquery.ajax.returns(postPromise);
+
+        return FirstFactorValidator.validate("username", "password", "http://redirect", jqueryMock.jquery as any)
+            .then(function () {
+                return BluebirdPromise.reject(new Error("First factor validation successfully finished while it should have not."));
+            }, function (err: Error) {
+                Assert.equal(errorMessage, err.message);
+                return BluebirdPromise.resolve();
+            });
+    }
+
+    describe("should fail first factor validation", () => {
+        it("should fail with error", () => {
+            return should_fail_first_factor_validation("Authentication failed. Please check your credentials.");
+        });
+    });
+});
\ No newline at end of file
diff --git a/themes/squares/client/test/mocks/NotifierStub.ts b/themes/squares/client/test/mocks/NotifierStub.ts
new file mode 100644
index 00000000..9c268d66
--- /dev/null
+++ b/themes/squares/client/test/mocks/NotifierStub.ts
@@ -0,0 +1,33 @@
+
+import Sinon = require("sinon");
+import { INotifier } from "../../src/lib/INotifier";
+
+export class NotifierStub implements INotifier {
+  successStub: Sinon.SinonStub;
+  errorStub: Sinon.SinonStub;
+  warnStub: Sinon.SinonStub;
+  infoStub: Sinon.SinonStub;
+
+  constructor() {
+    this.successStub = Sinon.stub();
+    this.errorStub = Sinon.stub();
+    this.warnStub = Sinon.stub();
+    this.infoStub = Sinon.stub();
+  }
+
+  success(msg: string) {
+    this.successStub();
+  }
+
+  error(msg: string) {
+    this.errorStub();
+  }
+
+  warning(msg: string) {
+    this.warnStub();
+  }
+
+  info(msg: string) {
+    this.infoStub();
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/client/test/mocks/jquery.ts b/themes/squares/client/test/mocks/jquery.ts
new file mode 100644
index 00000000..273f9086
--- /dev/null
+++ b/themes/squares/client/test/mocks/jquery.ts
@@ -0,0 +1,59 @@
+
+import sinon = require("sinon");
+import jquery = require("jquery");
+
+
+export interface JQueryMock extends sinon.SinonStub {
+    get: sinon.SinonStub;
+    post: sinon.SinonStub;
+    ajax: sinon.SinonStub;
+    notify: sinon.SinonStub;
+}
+
+export interface JQueryElementsMock {
+    ready: sinon.SinonStub;
+    show: sinon.SinonStub;
+    hide: sinon.SinonStub;
+    html: sinon.SinonStub;
+    addClass: sinon.SinonStub;
+    removeClass: sinon.SinonStub;
+    fadeIn: sinon.SinonStub;
+    fadeOut: sinon.SinonStub;
+    on: sinon.SinonStub;
+}
+
+export interface JQueryDeferredMock {
+    done: sinon.SinonStub;
+    fail: sinon.SinonStub;
+}
+
+export function JQueryMock(): { jquery: JQueryMock, element: JQueryElementsMock } {
+    const jquery = sinon.stub() as any;
+    const jqueryInstance: JQueryElementsMock = {
+        ready: sinon.stub(),
+        show: sinon.stub(),
+        hide: sinon.stub(),
+        html: sinon.stub(),
+        addClass: sinon.stub(),
+        removeClass: sinon.stub(),
+        fadeIn: sinon.stub(),
+        fadeOut: sinon.stub(),
+        on: sinon.stub()
+    };
+    jquery.ajax = sinon.stub();
+    jquery.get = sinon.stub();
+    jquery.post = sinon.stub();
+    jquery.notify = sinon.stub();
+    jquery.returns(jqueryInstance);
+    return {
+        jquery: jquery,
+        element: jqueryInstance
+    };
+}
+
+export function JQueryDeferredMock(): JQueryDeferredMock {
+    return {
+        done: sinon.stub(),
+        fail: sinon.stub()
+    };
+}
diff --git a/themes/squares/client/test/mocks/u2f-api.ts b/themes/squares/client/test/mocks/u2f-api.ts
new file mode 100644
index 00000000..d123f6a9
--- /dev/null
+++ b/themes/squares/client/test/mocks/u2f-api.ts
@@ -0,0 +1,14 @@
+
+import sinon = require("sinon");
+
+export interface U2FApiMock {
+    sign: sinon.SinonStub;
+    register: sinon.SinonStub;
+}
+
+export function U2FApiMock(): U2FApiMock {
+    return {
+        sign: sinon.stub(),
+        register: sinon.stub()
+    };
+}
\ No newline at end of file
diff --git a/themes/squares/client/test/secondfactor/TOTPValidator.test.ts b/themes/squares/client/test/secondfactor/TOTPValidator.test.ts
new file mode 100644
index 00000000..5dd6f15c
--- /dev/null
+++ b/themes/squares/client/test/secondfactor/TOTPValidator.test.ts
@@ -0,0 +1,37 @@
+
+import TOTPValidator = require("../../src/lib/secondfactor/TOTPValidator");
+import JQueryMock = require("../mocks/jquery");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+
+describe("test TOTPValidator", function () {
+  it("should initiate an identity check successfully", () => {
+    const postPromise = JQueryMock.JQueryDeferredMock();
+    postPromise.done.yields({ redirect: "https://home.test.url" });
+    postPromise.done.returns(postPromise);
+
+    const jqueryMock = JQueryMock.JQueryMock();
+    jqueryMock.jquery.ajax.returns(postPromise);
+
+    return TOTPValidator.validate("totp_token", jqueryMock.jquery as any);
+  });
+
+  it("should fail validating TOTP token", () => {
+    const errorMessage = "Error while validating TOTP token";
+
+    const postPromise = JQueryMock.JQueryDeferredMock();
+    postPromise.fail.yields(undefined, errorMessage);
+    postPromise.done.returns(postPromise);
+
+    const jqueryMock = JQueryMock.JQueryMock();
+    jqueryMock.jquery.ajax.returns(postPromise);
+
+    return TOTPValidator.validate("totp_token", jqueryMock.jquery as any)
+      .then(function () {
+        return BluebirdPromise.reject(new Error("Registration successfully finished while it should have not."));
+      }, function (err: Error) {
+        Assert.equal(errorMessage, err.message);
+        return BluebirdPromise.resolve();
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/client/test/totp-register/totp-register.test.ts b/themes/squares/client/test/totp-register/totp-register.test.ts
new file mode 100644
index 00000000..86fc455a
--- /dev/null
+++ b/themes/squares/client/test/totp-register/totp-register.test.ts
@@ -0,0 +1,31 @@
+
+import sinon = require("sinon");
+import assert = require("assert");
+
+import UISelector = require("../../src/lib/totp-register/ui-selector");
+import TOTPRegister = require("../../src/lib/totp-register/totp-register");
+
+describe("test totp-register", function() {
+    let jqueryMock: any;
+    let windowMock: any;
+    before(function() {
+        jqueryMock = sinon.stub();
+        windowMock = {
+            QRCode: sinon.spy()
+        };
+    });
+
+    it("should create qrcode in page", function() {
+        const mock = {
+            text: sinon.stub(),
+            empty: sinon.stub(),
+            get: sinon.stub()
+        };
+        jqueryMock.withArgs(UISelector.QRCODE_ID_SELECTOR).returns(mock);
+
+        TOTPRegister.default(windowMock, jqueryMock);
+
+        assert(mock.text.calledOnce);
+        assert(mock.empty.calledOnce);
+    });
+});
\ No newline at end of file
diff --git a/themes/squares/client/tsconfig.json b/themes/squares/client/tsconfig.json
new file mode 100644
index 00000000..0bb4d62f
--- /dev/null
+++ b/themes/squares/client/tsconfig.json
@@ -0,0 +1,24 @@
+{
+    "compilerOptions": {
+        "module": "commonjs",
+        "target": "es6",
+        "moduleResolution": "node",
+        "noImplicitAny": true,
+        "sourceMap": true,
+        "removeComments": true,
+        "outDir": "../dist",
+        "baseUrl": ".",
+        "paths": {
+            "*": [
+                "./types/*",
+                "../shared/types/*"
+            ]
+        }
+    },
+    "include": [
+        "src/**/*"
+    ],
+    "exclude": [
+        "test/**/*"
+    ]
+}
diff --git a/themes/squares/client/tslint.json b/themes/squares/client/tslint.json
new file mode 100644
index 00000000..c2c1b750
--- /dev/null
+++ b/themes/squares/client/tslint.json
@@ -0,0 +1,60 @@
+{
+    "rules": {
+        "class-name": true,
+        "comment-format": [
+            true,
+            "check-space"
+        ],
+        "indent": [
+            true,
+            "spaces"
+        ],
+        "one-line": [
+            true,
+            "check-open-brace",
+            "check-whitespace"
+        ],
+        "no-var-keyword": true,
+        "quotemark": [
+            true,
+            "double",
+            "avoid-escape"
+        ],
+        "semicolon": [
+            true,
+            "always",
+            "ignore-bound-class-methods"
+        ],
+        "whitespace": [
+            true,
+            "check-branch",
+            "check-decl",
+            "check-operator",
+            "check-module",
+            "check-separator",
+            "check-type"
+        ],
+        "typedef-whitespace": [
+            true,
+            {
+                "call-signature": "nospace",
+                "index-signature": "nospace",
+                "parameter": "nospace",
+                "property-declaration": "nospace",
+                "variable-declaration": "nospace"
+            },
+            {
+                "call-signature": "onespace",
+                "index-signature": "onespace",
+                "parameter": "onespace",
+                "property-declaration": "onespace",
+                "variable-declaration": "onespace"
+            }
+        ],
+        "no-internal-module": true,
+        "no-trailing-whitespace": true,
+        "no-null-keyword": true,
+        "prefer-const": true,
+        "jsdoc-format": true
+    }
+}
diff --git a/themes/squares/server/.directory b/themes/squares/server/.directory
new file mode 100644
index 00000000..b7754766
--- /dev/null
+++ b/themes/squares/server/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,58,20
+Version=3
+ViewMode=1
diff --git a/themes/squares/server/src/index.ts b/themes/squares/server/src/index.ts
new file mode 100755
index 00000000..fcbf4d02
--- /dev/null
+++ b/themes/squares/server/src/index.ts
@@ -0,0 +1,28 @@
+#! /usr/bin/env node
+
+import Server from "./lib/Server";
+import { GlobalDependencies } from "../types/Dependencies";
+import YAML = require("yamljs");
+
+const configurationFilepath = process.argv[2];
+if (!configurationFilepath) {
+  console.log("No config file has been provided.");
+  console.log("Usage: authelia <config>");
+  process.exit(0);
+}
+
+const yamlContent = YAML.load(configurationFilepath);
+
+const deps: GlobalDependencies = {
+  u2f: require("u2f"),
+  ldapjs: require("ldapjs"),
+  session: require("express-session"),
+  winston: require("winston"),
+  speakeasy: require("speakeasy"),
+  nedb: require("nedb"),
+  ConnectRedis: require("connect-redis"),
+  Redis: require("redis")
+};
+
+const server = new Server(deps);
+server.start(yamlContent, deps);
diff --git a/themes/squares/server/src/lib/.directory b/themes/squares/server/src/lib/.directory
new file mode 100644
index 00000000..006b379a
--- /dev/null
+++ b/themes/squares/server/src/lib/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,59,13
+Version=3
+ViewMode=1
diff --git a/themes/squares/server/src/lib/AuthenticationSessionHandler.ts b/themes/squares/server/src/lib/AuthenticationSessionHandler.ts
new file mode 100644
index 00000000..57361bf8
--- /dev/null
+++ b/themes/squares/server/src/lib/AuthenticationSessionHandler.ts
@@ -0,0 +1,45 @@
+
+
+import express = require("express");
+import U2f = require("u2f");
+import BluebirdPromise = require("bluebird");
+import { AuthenticationSession } from "../../types/AuthenticationSession";
+import { IRequestLogger } from "./logging/IRequestLogger";
+import { Level } from "./authentication/Level";
+
+const INITIAL_AUTHENTICATION_SESSION: AuthenticationSession = {
+  keep_me_logged_in: false,
+  authentication_level: Level.NOT_AUTHENTICATED,
+  last_activity_datetime: undefined,
+  userid: undefined,
+  email: undefined,
+  groups: [],
+  register_request: undefined,
+  sign_request: undefined,
+  identity_check: undefined,
+  redirect: undefined
+};
+
+export class AuthenticationSessionHandler {
+  static reset(req: express.Request): void {
+    req.session.auth = Object.assign({}, INITIAL_AUTHENTICATION_SESSION, {});
+
+    // Initialize last activity with current time
+    req.session.auth.last_activity_datetime = new Date().getTime();
+  }
+
+  static get(req: express.Request, logger: IRequestLogger): AuthenticationSession {
+    if (!req.session) {
+      const errorMsg = "Something is wrong with session cookies. Please check Redis is running and Authelia can connect to it.";
+      logger.error(req, errorMsg);
+      throw new Error(errorMsg);
+    }
+
+    if (!req.session.auth) {
+      logger.debug(req, "Authentication session %s was undefined. Resetting.", req.sessionID);
+      AuthenticationSessionHandler.reset(req);
+    }
+
+    return req.session.auth;
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/ErrorReplies.ts b/themes/squares/server/src/lib/ErrorReplies.ts
new file mode 100644
index 00000000..f1c5f4fd
--- /dev/null
+++ b/themes/squares/server/src/lib/ErrorReplies.ts
@@ -0,0 +1,49 @@
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import { IRequestLogger } from "./logging/IRequestLogger";
+
+function replyWithError(req: express.Request, res: express.Response,
+  code: number, logger: IRequestLogger, body?: Object): (err: Error) => void {
+  return function (err: Error): void {
+    if (req.originalUrl.startsWith("/api/") || code == 200) {
+      logger.error(req, "Reply with error %d: %s", code, err.message);
+      logger.debug(req, "%s", err.stack);
+      res.status(code);
+      res.send(body);
+    }
+    else {
+      logger.error(req, "Redirect to error %d: %s", code, err.message);
+      logger.debug(req, "%s", err.stack);
+      res.redirect("/error/" + code);
+    }
+  };
+}
+
+export function redirectTo(redirectUrl: string, req: express.Request,
+  res: express.Response, logger: IRequestLogger) {
+  return function(err: Error) {
+    logger.error(req, "Error: %s", err.message);
+    logger.debug(req, "Redirecting to %s", redirectUrl);
+    res.redirect(redirectUrl);
+  };
+}
+
+export function replyWithError400(req: express.Request,
+  res: express.Response, logger: IRequestLogger) {
+  return replyWithError(req, res, 400, logger);
+}
+
+export function replyWithError401(req: express.Request,
+  res: express.Response, logger: IRequestLogger) {
+  return replyWithError(req, res, 401, logger);
+}
+
+export function replyWithError403(req: express.Request,
+  res: express.Response, logger: IRequestLogger) {
+  return replyWithError(req, res, 403, logger);
+}
+
+export function replyWithError200(req: express.Request,
+  res: express.Response, logger: IRequestLogger, message: string) {
+  return replyWithError(req, res, 200, logger, { error: message });
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/Exceptions.ts b/themes/squares/server/src/lib/Exceptions.ts
new file mode 100644
index 00000000..83fa4eb6
--- /dev/null
+++ b/themes/squares/server/src/lib/Exceptions.ts
@@ -0,0 +1,88 @@
+
+export class LdapSearchError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "LdapSearchError";
+    (<any>Object).setPrototypeOf(this, LdapSearchError.prototype);
+  }
+}
+
+export class LdapBindError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "LdapBindError";
+    (<any>Object).setPrototypeOf(this, LdapBindError.prototype);
+  }
+}
+
+export class LdapError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "LdapError";
+    (<any>Object).setPrototypeOf(this, LdapError.prototype);
+  }
+}
+
+export class IdentityError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "IdentityError";
+    (<any>Object).setPrototypeOf(this, IdentityError.prototype);
+  }
+}
+
+export class AccessDeniedError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "AccessDeniedError";
+    (<any>Object).setPrototypeOf(this, AccessDeniedError.prototype);
+  }
+}
+
+export class AuthenticationRegulationError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "AuthenticationRegulationError";
+    (<any>Object).setPrototypeOf(this, AuthenticationRegulationError.prototype);
+  }
+}
+
+export class InvalidTOTPError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "InvalidTOTPError";
+    (<any>Object).setPrototypeOf(this, InvalidTOTPError.prototype);
+  }
+}
+
+export class NotAuthenticatedError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "NotAuthenticatedError";
+    (<any>Object).setPrototypeOf(this, NotAuthenticatedError.prototype);
+  }
+}
+
+export class NotAuthorizedError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "NotAuthanticatedError";
+    (<any>Object).setPrototypeOf(this, NotAuthorizedError.prototype);
+  }
+}
+
+export class FirstFactorValidationError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "FirstFactorValidationError";
+    (<any>Object).setPrototypeOf(this, FirstFactorValidationError.prototype);
+  }
+}
+
+export class SecondFactorValidationError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "SecondFactorValidationError";
+    (<any>Object).setPrototypeOf(this, FirstFactorValidationError.prototype);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/FirstFactorValidator.ts b/themes/squares/server/src/lib/FirstFactorValidator.ts
new file mode 100644
index 00000000..23106000
--- /dev/null
+++ b/themes/squares/server/src/lib/FirstFactorValidator.ts
@@ -0,0 +1,20 @@
+
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import objectPath = require("object-path");
+import Exceptions = require("./Exceptions");
+import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
+import { IRequestLogger } from "./logging/IRequestLogger";
+import { Level } from "./authentication/Level";
+
+export function validate(req: express.Request, logger: IRequestLogger): BluebirdPromise<void> {
+  return new BluebirdPromise(function (resolve, reject) {
+    const authSession = AuthenticationSessionHandler.get(req, logger);
+
+    if (!authSession.userid || authSession.authentication_level < Level.ONE_FACTOR)
+      return reject(new Exceptions.FirstFactorValidationError(
+        "First factor has not been validated yet."));
+
+    resolve();
+  });
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/IdentityCheckMiddleware.spec.ts b/themes/squares/server/src/lib/IdentityCheckMiddleware.spec.ts
new file mode 100644
index 00000000..842ed6bc
--- /dev/null
+++ b/themes/squares/server/src/lib/IdentityCheckMiddleware.spec.ts
@@ -0,0 +1,176 @@
+
+import sinon = require("sinon");
+import IdentityValidator = require("./IdentityCheckMiddleware");
+import { AuthenticationSessionHandler }
+  from "./AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../types/AuthenticationSession";
+import { UserDataStore } from "./storage/UserDataStore";
+import exceptions = require("./Exceptions");
+import { ServerVariables } from "./ServerVariables";
+import Assert = require("assert");
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import ExpressMock = require("./stubs/express.spec");
+import NotifierMock = require("./notifiers/NotifierStub.spec");
+import { IdentityValidableStub } from "./IdentityValidableStub.spec";
+import { RequestLoggerStub } from "./logging/RequestLoggerStub.spec";
+import { ServerVariablesMock, ServerVariablesMockBuilder }
+  from "./ServerVariablesMockBuilder.spec";
+import { PRE_VALIDATION_TEMPLATE }
+  from "./IdentityCheckPreValidationTemplate";
+
+
+describe("IdentityCheckMiddleware", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let app: express.Application;
+  let app_get: sinon.SinonStub;
+  let app_post: sinon.SinonStub;
+  let identityValidable: IdentityValidableStub;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req = ExpressMock.RequestMock();
+    res = ExpressMock.ResponseMock();
+
+    req.headers = {};
+    req.originalUrl = "/non-api/xxx";
+    req.session = {};
+
+    req.query = {};
+    req.app = {};
+
+    identityValidable = new IdentityValidableStub();
+
+    mocks.notifier.notifyStub.returns(BluebirdPromise.resolve());
+    mocks.userDataStore.produceIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve());
+    mocks.userDataStore.consumeIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve({ userId: "user" }));
+
+    app = express();
+    app_get = sinon.stub(app, "get");
+    app_post = sinon.stub(app, "post");
+  });
+
+  afterEach(function () {
+    app_get.restore();
+    app_post.restore();
+  });
+
+  describe("test start GET", function () {
+    it("should redirect to error 401 if pre validation initialization \
+throws a first factor error", function () {
+        identityValidable.preValidationInitStub.returns(BluebirdPromise.reject(
+          new exceptions.FirstFactorValidationError(
+            "Error during prevalidation")));
+        const callback = IdentityValidator.get_start_validation(
+          identityValidable, "/endpoint", vars);
+
+        return callback(req as any, res as any, undefined)
+          .then(() => {
+            Assert(res.redirect.calledWith("/error/401"));
+          });
+      });
+
+    // In that case we answer with 200 to avoid user enumeration.
+    it("should send 200 if email is missing in provided identity", function () {
+      const identity = { userid: "abc" };
+
+      identityValidable.preValidationInitStub
+        .returns(BluebirdPromise.resolve(identity));
+      const callback = IdentityValidator
+        .get_start_validation(identityValidable, "/endpoint", vars);
+
+      return callback(req as any, res as any, undefined)
+        .then(function () {
+          Assert(identityValidable.preValidationResponseStub.called);
+        });
+    });
+
+    // In that case we answer with 200 to avoid user enumeration.
+    it("should send 200 if userid is missing in provided identity",
+      function () {
+        const endpoint = "/protected";
+        const identity = { email: "abc@example.com" };
+
+        identityValidable.preValidationInitStub
+          .returns(BluebirdPromise.resolve(identity));
+        const callback = IdentityValidator
+          .get_start_validation(identityValidable, "/endpoint", vars);
+
+        return callback(req as any, res as any, undefined)
+          .then(function () {
+            Assert(identityValidable.preValidationResponseStub.called);
+          });
+      });
+
+    it("should issue a token, send an email and return 204", function () {
+      const endpoint = "/protected";
+      const identity = { userid: "user", email: "abc@example.com" };
+      req.get = sinon.stub().withArgs("Host").returns("localhost");
+
+      identityValidable.preValidationInitStub
+        .returns(BluebirdPromise.resolve(identity));
+      const callback = IdentityValidator
+        .get_start_validation(identityValidable, "/finish_endpoint", vars);
+
+      return callback(req as any, res as any, undefined)
+        .then(function () {
+          Assert(mocks.notifier.notifyStub.calledOnce);
+          Assert(mocks.userDataStore.produceIdentityValidationTokenStub
+            .calledOnce);
+          Assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub
+            .getCall(0).args[0], "user");
+          Assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub
+            .getCall(0).args[3], 240000);
+        });
+    });
+  });
+
+
+
+  describe("test finish GET", function () {
+    it("should send 401 if no identity_token is provided", () => {
+      const callback = IdentityValidator
+        .get_finish_validation(identityValidable, vars);
+
+      return callback(req as any, res as any, undefined)
+        .then(function () {
+          Assert(res.redirect.calledWith("/error/401"));
+        });
+    });
+
+    it("should call postValidation if identity_token is provided and still \
+valid", function () {
+        req.query.identity_token = "token";
+
+        const callback = IdentityValidator
+          .get_finish_validation(identityValidable, vars);
+        return callback(req as any, res as any, undefined);
+      });
+
+    it("should return 401 if identity_token is provided but invalid",
+      function () {
+        req.query.identity_token = "token";
+
+        identityValidable.postValidationInitStub
+          .returns(BluebirdPromise.resolve());
+        mocks.userDataStore.consumeIdentityValidationTokenStub.reset();
+        mocks.userDataStore.consumeIdentityValidationTokenStub
+          .returns(BluebirdPromise.reject(new Error("Invalid token")));
+
+        const callback = IdentityValidator
+          .get_finish_validation(identityValidable, vars);
+        return callback(req as any, res as any, undefined)
+          .then(() => {
+            Assert(res.redirect.calledWith("/error/401"));
+          });
+      });
+  });
+});
diff --git a/themes/squares/server/src/lib/IdentityCheckMiddleware.ts b/themes/squares/server/src/lib/IdentityCheckMiddleware.ts
new file mode 100644
index 00000000..e72ea4db
--- /dev/null
+++ b/themes/squares/server/src/lib/IdentityCheckMiddleware.ts
@@ -0,0 +1,138 @@
+import objectPath = require("object-path");
+import randomstring = require("randomstring");
+import BluebirdPromise = require("bluebird");
+import util = require("util");
+import Exceptions = require("./Exceptions");
+import fs = require("fs");
+import ejs = require("ejs");
+import { IUserDataStore } from "./storage/IUserDataStore";
+import Express = require("express");
+import ErrorReplies = require("./ErrorReplies");
+import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../types/AuthenticationSession";
+import { ServerVariables } from "./ServerVariables";
+import { IdentityValidable } from "./IdentityValidable";
+
+import Identity = require("../../types/Identity");
+import { IdentityValidationDocument }
+  from "./storage/IdentityValidationDocument";
+
+const filePath = __dirname + "/../resources/email-template.ejs";
+const email_template = fs.readFileSync(filePath, "utf8");
+
+function createAndSaveToken(userid: string, challenge: string,
+  userDataStore: IUserDataStore): BluebirdPromise<string> {
+
+  const five_minutes = 4 * 60 * 1000;
+  const token = randomstring.generate({ length: 64 });
+  const that = this;
+
+  return userDataStore.produceIdentityValidationToken(userid, token, challenge,
+    five_minutes)
+    .then(function () {
+      return BluebirdPromise.resolve(token);
+    });
+}
+
+function consumeToken(token: string, challenge: string,
+  userDataStore: IUserDataStore)
+  : BluebirdPromise<IdentityValidationDocument> {
+  return userDataStore.consumeIdentityValidationToken(token, challenge);
+}
+
+export function register(app: Express.Application,
+  pre_validation_endpoint: string,
+  post_validation_endpoint: string,
+  handler: IdentityValidable,
+  vars: ServerVariables) {
+
+  app.get(pre_validation_endpoint,
+    get_start_validation(handler, post_validation_endpoint, vars));
+  app.get(post_validation_endpoint,
+    get_finish_validation(handler, vars));
+}
+
+function checkIdentityToken(req: Express.Request, identityToken: string)
+  : BluebirdPromise<void> {
+  if (!identityToken)
+    return BluebirdPromise.reject(
+      new Exceptions.AccessDeniedError("No identity token provided"));
+  return BluebirdPromise.resolve();
+}
+
+export function get_finish_validation(handler: IdentityValidable,
+  vars: ServerVariables)
+  : Express.RequestHandler {
+
+  return function (req: Express.Request, res: Express.Response)
+    : BluebirdPromise<void> {
+
+    let authSession: AuthenticationSession;
+    const identityToken = objectPath.get<Express.Request, string>(
+      req, "query.identity_token");
+    vars.logger.debug(req, "Identity token provided is %s", identityToken);
+
+    return checkIdentityToken(req, identityToken)
+      .then(() => {
+        authSession = AuthenticationSessionHandler.get(req, vars.logger);
+        return handler.postValidationInit(req);
+      })
+      .then(() => {
+        return consumeToken(identityToken, handler.challenge(),
+          vars.userDataStore);
+      })
+      .then((doc: IdentityValidationDocument) => {
+        authSession.identity_check = {
+          challenge: handler.challenge(),
+          userid: doc.userId
+        };
+        handler.postValidationResponse(req, res);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError401(req, res, vars.logger));
+  };
+}
+
+export function get_start_validation(handler: IdentityValidable,
+  postValidationEndpoint: string,
+  vars: ServerVariables)
+  : Express.RequestHandler {
+  return function (req: Express.Request, res: Express.Response)
+    : BluebirdPromise<void> {
+    let identity: Identity.Identity;
+
+    return handler.preValidationInit(req)
+      .then((id: Identity.Identity) => {
+        identity = id;
+        const email = identity.email;
+        const userid = identity.userid;
+        vars.logger.info(req, "Start identity validation of user \"%s\"",
+          userid);
+
+        if (!(email && userid))
+          return BluebirdPromise.reject(new Exceptions.IdentityError(
+            "Missing user id or email address"));
+
+        return createAndSaveToken(userid, handler.challenge(),
+          vars.userDataStore);
+      })
+      .then((token) => {
+        const host = req.get("Host");
+        const link_url = util.format("https://%s%s?identity_token=%s", host,
+          postValidationEndpoint, token);
+        vars.logger.info(req, "Notification sent to user \"%s\"",
+          identity.userid);
+        return vars.notifier.notify(identity.email, handler.mailSubject(),
+          link_url);
+      })
+      .then(() => {
+        handler.preValidationResponse(req, res);
+        return BluebirdPromise.resolve();
+      })
+      .catch(Exceptions.IdentityError, (err: Error) => {
+        handler.preValidationResponse(req, res);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError401(req, res, vars.logger));
+  };
+}
diff --git a/themes/squares/server/src/lib/IdentityCheckPreValidationTemplate.ts b/themes/squares/server/src/lib/IdentityCheckPreValidationTemplate.ts
new file mode 100644
index 00000000..0161ce40
--- /dev/null
+++ b/themes/squares/server/src/lib/IdentityCheckPreValidationTemplate.ts
@@ -0,0 +1,3 @@
+
+
+export const PRE_VALIDATION_TEMPLATE = "need-identity-validation";
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/IdentityValidable.ts b/themes/squares/server/src/lib/IdentityValidable.ts
new file mode 100644
index 00000000..075580c9
--- /dev/null
+++ b/themes/squares/server/src/lib/IdentityValidable.ts
@@ -0,0 +1,19 @@
+import Bluebird = require("bluebird");
+import Identity = require("../../types/Identity");
+
+// IdentityValidator allows user to go through a identity validation process
+// in two steps:
+// - Request an operation to be performed (password reset, registration).
+// - Confirm operation with email.
+
+export interface IdentityValidable {
+  challenge(): string;
+  preValidationInit(req: Express.Request): Bluebird<Identity.Identity>;
+  postValidationInit(req: Express.Request): Bluebird<void>;
+
+  // Serves a page after identity check request
+  preValidationResponse(req: Express.Request, res: Express.Response): void;
+  // Serves the page if identity validated
+  postValidationResponse(req: Express.Request, res: Express.Response): void;
+  mailSubject(): string;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/IdentityValidableStub.spec.ts b/themes/squares/server/src/lib/IdentityValidableStub.spec.ts
new file mode 100644
index 00000000..20a97714
--- /dev/null
+++ b/themes/squares/server/src/lib/IdentityValidableStub.spec.ts
@@ -0,0 +1,52 @@
+
+import Sinon = require("sinon");
+import { IdentityValidable } from "./IdentityValidable";
+import express = require("express");
+import Bluebird = require("bluebird");
+import { Identity } from "../../types/Identity";
+
+
+export class IdentityValidableStub implements IdentityValidable {
+    challengeStub: Sinon.SinonStub;
+    preValidationInitStub: Sinon.SinonStub;
+    postValidationInitStub: Sinon.SinonStub;
+    preValidationResponseStub: Sinon.SinonStub;
+    postValidationResponseStub: Sinon.SinonStub;
+    mailSubjectStub: Sinon.SinonStub;
+
+    constructor() {
+        this.challengeStub = Sinon.stub();
+
+        this.preValidationInitStub = Sinon.stub();
+        this.postValidationInitStub = Sinon.stub();
+
+        this.preValidationResponseStub = Sinon.stub();
+        this.postValidationResponseStub = Sinon.stub();
+
+        this.mailSubjectStub = Sinon.stub();
+    }
+
+    challenge(): string {
+        return this.challengeStub();
+    }
+
+    preValidationInit(req: Express.Request): Bluebird<Identity> {
+        return this.preValidationInitStub(req);
+    }
+
+    postValidationInit(req: Express.Request): Bluebird<void> {
+        return this.postValidationInitStub(req);
+    }
+
+    preValidationResponse(req: Express.Request, res: Express.Response): void {
+        return this.preValidationResponseStub(req, res);
+    }
+
+    postValidationResponse(req: Express.Request, res: Express.Response): void {
+        return this.postValidationResponseStub(req, res);
+    }
+
+    mailSubject(): string {
+        return this.mailSubjectStub();
+    }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/Server.spec.ts b/themes/squares/server/src/lib/Server.spec.ts
new file mode 100644
index 00000000..36516325
--- /dev/null
+++ b/themes/squares/server/src/lib/Server.spec.ts
@@ -0,0 +1,81 @@
+
+import Assert = require("assert");
+import Sinon = require("sinon");
+import nedb = require("nedb");
+import express = require("express");
+import winston = require("winston");
+import speakeasy = require("speakeasy");
+import u2f = require("u2f");
+import session = require("express-session");
+import { Configuration } from "./configuration/schema/Configuration";
+import { GlobalDependencies } from "../../types/Dependencies";
+import Server from "./Server";
+import { LdapjsMock, LdapjsClientMock } from "./stubs/ldapjs.spec";
+
+
+describe("Server", function () {
+  let deps: GlobalDependencies;
+  let sessionMock: Sinon.SinonSpy;
+  let ldapjsMock: LdapjsMock;
+
+  before(function () {
+    sessionMock = Sinon.spy(session);
+    ldapjsMock = new LdapjsMock();
+
+    deps = {
+      speakeasy: speakeasy,
+      u2f: u2f,
+      nedb: nedb,
+      winston: winston,
+      ldapjs: ldapjsMock as any,
+      session: sessionMock as any,
+      ConnectRedis: Sinon.spy(),
+      Redis: Sinon.spy() as any
+    };
+  });
+
+
+  it("should set cookie scope to domain set in the config", function () {
+    const config: Configuration = {
+      port: 8081,
+      session: {
+        domain: "example.com",
+        secret: "secret"
+      },
+      authentication_backend: {
+        ldap: {
+          url: "http://ldap",
+          user: "user",
+          password: "password",
+          base_dn: "dc=example,dc=com"
+        },
+      },
+      notifier: {
+        email: {
+          username: "user@example.com",
+          password: "password",
+          sender: "test@authelia.com",
+          service: "gmail"
+        }
+      },
+      regulation: {
+        max_retries: 3,
+        ban_time: 5 * 60,
+        find_time: 5 * 60
+      },
+      storage: {
+        local: {
+          in_memory: true
+        }
+      }
+    };
+
+    const server = new Server(deps);
+    server.start(config, deps)
+      .then(function () {
+        Assert(sessionMock.calledOnce);
+        Assert.equal(sessionMock.getCall(0).args[0].cookie.domain, "example.com");
+        server.stop();
+      });
+  });
+});
diff --git a/themes/squares/server/src/lib/Server.ts b/themes/squares/server/src/lib/Server.ts
new file mode 100644
index 00000000..4090f629
--- /dev/null
+++ b/themes/squares/server/src/lib/Server.ts
@@ -0,0 +1,93 @@
+import BluebirdPromise = require("bluebird");
+import ObjectPath = require("object-path");
+
+import { Configuration } from "./configuration/schema/Configuration";
+import { GlobalDependencies } from "../../types/Dependencies";
+import { UserDataStore } from "./storage/UserDataStore";
+import { ConfigurationParser } from "./configuration/ConfigurationParser";
+import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder";
+import { GlobalLogger } from "./logging/GlobalLogger";
+import { RequestLogger } from "./logging/RequestLogger";
+import { ServerVariables } from "./ServerVariables";
+import { ServerVariablesInitializer } from "./ServerVariablesInitializer";
+import { Configurator } from "./web_server/Configurator";
+
+import * as Express from "express";
+import * as Path from "path";
+import * as http from "http";
+
+function clone(obj: any) {
+  return JSON.parse(JSON.stringify(obj));
+}
+
+export default class Server {
+  private httpServer: http.Server;
+  private globalLogger: GlobalLogger;
+  private requestLogger: RequestLogger;
+
+  constructor(deps: GlobalDependencies) {
+    this.globalLogger = new GlobalLogger(deps.winston);
+    this.requestLogger = new RequestLogger(deps.winston);
+  }
+
+  private displayConfigurations(configuration: Configuration) {
+    const displayableConfiguration: Configuration = clone(configuration);
+    const STARS = "*****";
+
+    if (displayableConfiguration.authentication_backend.ldap) {
+      displayableConfiguration.authentication_backend.ldap.password = STARS;
+    }
+
+    displayableConfiguration.session.secret = STARS;
+    if (displayableConfiguration.notifier && displayableConfiguration.notifier.email)
+      displayableConfiguration.notifier.email.password = STARS;
+    if (displayableConfiguration.notifier && displayableConfiguration.notifier.smtp)
+      displayableConfiguration.notifier.smtp.password = STARS;
+
+    this.globalLogger.debug("User configuration is %s",
+      JSON.stringify(displayableConfiguration, undefined, 2));
+  }
+
+  private setup(config: Configuration, app: Express.Application, deps: GlobalDependencies): BluebirdPromise<void> {
+    const that = this;
+    return ServerVariablesInitializer.initialize(
+      config, this.globalLogger, this.requestLogger, deps)
+      .then(function (vars: ServerVariables) {
+        Configurator.configure(config, app, vars, deps);
+        return BluebirdPromise.resolve();
+      });
+  }
+
+  private startServer(app: Express.Application, port: number) {
+    const that = this;
+    that.globalLogger.info("Starting Authelia...");
+    return new BluebirdPromise<void>((resolve, reject) => {
+      this.httpServer = app.listen(port, function (err: string) {
+        that.globalLogger.info("Listening on port %d...", port);
+        resolve();
+      });
+    });
+  }
+
+  start(configuration: Configuration, deps: GlobalDependencies)
+    : BluebirdPromise<void> {
+    const that = this;
+    const app = Express();
+
+    const appConfiguration = ConfigurationParser.parse(configuration);
+
+    // by default the level of logs is info
+    deps.winston.level = appConfiguration.logs_level;
+    this.displayConfigurations(appConfiguration);
+
+    return this.setup(appConfiguration, app, deps)
+      .then(function () {
+        return that.startServer(app, appConfiguration.port);
+      });
+  }
+
+  stop() {
+    this.httpServer.close();
+  }
+}
+
diff --git a/themes/squares/server/src/lib/ServerVariables.ts b/themes/squares/server/src/lib/ServerVariables.ts
new file mode 100644
index 00000000..cd3dd6dc
--- /dev/null
+++ b/themes/squares/server/src/lib/ServerVariables.ts
@@ -0,0 +1,21 @@
+import { IRequestLogger } from "./logging/IRequestLogger";
+import { ITotpHandler } from "./authentication/totp/ITotpHandler";
+import { IU2fHandler } from "./authentication/u2f/IU2fHandler";
+import { IUserDataStore } from "./storage/IUserDataStore";
+import { INotifier } from "./notifiers/INotifier";
+import { IRegulator } from "./regulation/IRegulator";
+import { Configuration } from "./configuration/schema/Configuration";
+import { IAuthorizer } from "./authorization/IAuthorizer";
+import { IUsersDatabase } from "./authentication/backends/IUsersDatabase";
+
+export interface ServerVariables {
+  logger: IRequestLogger;
+  usersDatabase: IUsersDatabase;
+  totpHandler: ITotpHandler;
+  u2f: IU2fHandler;
+  userDataStore: IUserDataStore;
+  notifier: INotifier;
+  regulator: IRegulator;
+  config: Configuration;
+  authorizer: IAuthorizer;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/ServerVariablesInitializer.ts b/themes/squares/server/src/lib/ServerVariablesInitializer.ts
new file mode 100644
index 00000000..df79238c
--- /dev/null
+++ b/themes/squares/server/src/lib/ServerVariablesInitializer.ts
@@ -0,0 +1,116 @@
+
+import winston = require("winston");
+import BluebirdPromise = require("bluebird");
+import U2F = require("u2f");
+import Nodemailer = require("nodemailer");
+
+import { IRequestLogger } from "./logging/IRequestLogger";
+import { RequestLogger } from "./logging/RequestLogger";
+
+import { TotpHandler } from "./authentication/totp/TotpHandler";
+import { ITotpHandler } from "./authentication/totp/ITotpHandler";
+import { NotifierFactory } from "./notifiers/NotifierFactory";
+import { MailSenderBuilder } from "./notifiers/MailSenderBuilder";
+import { LdapUsersDatabase } from "./authentication/backends/ldap/LdapUsersDatabase";
+import { ConnectorFactory } from "./authentication/backends/ldap/connector/ConnectorFactory";
+
+import { IUserDataStore } from "./storage/IUserDataStore";
+import { UserDataStore } from "./storage/UserDataStore";
+import { INotifier } from "./notifiers/INotifier";
+import { Regulator } from "./regulation/Regulator";
+import { IRegulator } from "./regulation/IRegulator";
+import Configuration = require("./configuration/schema/Configuration");
+import { CollectionFactoryFactory } from "./storage/CollectionFactoryFactory";
+import { ICollectionFactory } from "./storage/ICollectionFactory";
+import { MongoCollectionFactory } from "./storage/mongo/MongoCollectionFactory";
+import { IMongoClient } from "./connectors/mongo/IMongoClient";
+
+import { GlobalDependencies } from "../../types/Dependencies";
+import { ServerVariables } from "./ServerVariables";
+import { MongoClient } from "./connectors/mongo/MongoClient";
+import { IGlobalLogger } from "./logging/IGlobalLogger";
+import { SessionFactory } from "./authentication/backends/ldap/SessionFactory";
+import { IUsersDatabase } from "./authentication/backends/IUsersDatabase";
+import { FileUsersDatabase } from "./authentication/backends/file/FileUsersDatabase";
+import { Authorizer } from "./authorization/Authorizer";
+
+class UserDataStoreFactory {
+  static create(config: Configuration.Configuration, globalLogger: IGlobalLogger): BluebirdPromise<UserDataStore> {
+    if (config.storage.local) {
+      const nedbOptions: Nedb.DataStoreOptions = {
+        filename: config.storage.local.path,
+        inMemoryOnly: config.storage.local.in_memory
+      };
+      const collectionFactory = CollectionFactoryFactory.createNedb(nedbOptions);
+      return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
+    }
+    else if (config.storage.mongo) {
+      const mongoClient = new MongoClient(
+        config.storage.mongo,
+        globalLogger);
+      const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient);
+      return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
+    }
+
+    return BluebirdPromise.reject(new Error("Storage backend incorrectly configured."));
+  }
+}
+
+export class ServerVariablesInitializer {
+  static createUsersDatabase(
+    config: Configuration.Configuration,
+    deps: GlobalDependencies)
+    : IUsersDatabase {
+
+    if (config.authentication_backend.ldap) {
+      const ldapConfig = config.authentication_backend.ldap;
+      return new LdapUsersDatabase(
+        new SessionFactory(
+          ldapConfig,
+          new ConnectorFactory(ldapConfig, deps.ldapjs),
+          deps.winston
+        ),
+        ldapConfig
+      );
+    }
+    else if (config.authentication_backend.file) {
+      return new FileUsersDatabase(config.authentication_backend.file);
+    }
+  }
+
+  static initialize(
+    config: Configuration.Configuration,
+    globalLogger: IGlobalLogger,
+    requestLogger: IRequestLogger,
+    deps: GlobalDependencies)
+    : BluebirdPromise<ServerVariables> {
+
+    const mailSenderBuilder =
+      new MailSenderBuilder(Nodemailer);
+    const notifier = NotifierFactory.build(
+      config.notifier, mailSenderBuilder);
+    const authorizer = new Authorizer(config.access_control, deps.winston);
+    const totpHandler = new TotpHandler(deps.speakeasy);
+    const usersDatabase = this.createUsersDatabase(
+      config, deps);
+
+    return UserDataStoreFactory.create(config, globalLogger)
+      .then(function (userDataStore: UserDataStore) {
+        const regulator = new Regulator(userDataStore, config.regulation.max_retries,
+          config.regulation.find_time, config.regulation.ban_time);
+
+        const variables: ServerVariables = {
+          authorizer: authorizer,
+          config: config,
+          usersDatabase: usersDatabase,
+          logger: requestLogger,
+          notifier: notifier,
+          regulator: regulator,
+          totpHandler: totpHandler,
+          u2f: deps.u2f,
+          userDataStore: userDataStore
+        };
+        return BluebirdPromise.resolve(variables);
+      });
+  }
+}
diff --git a/themes/squares/server/src/lib/ServerVariablesMockBuilder.spec.ts b/themes/squares/server/src/lib/ServerVariablesMockBuilder.spec.ts
new file mode 100644
index 00000000..7874702a
--- /dev/null
+++ b/themes/squares/server/src/lib/ServerVariablesMockBuilder.spec.ts
@@ -0,0 +1,87 @@
+import { ServerVariables } from "./ServerVariables";
+
+import { Configuration } from "./configuration/schema/Configuration";
+import { IUsersDatabaseStub } from "./authentication/backends/IUsersDatabaseStub.spec";
+import { AuthorizerStub } from "./authorization/AuthorizerStub.spec";
+import { RequestLoggerStub } from "./logging/RequestLoggerStub.spec";
+import { NotifierStub } from "./notifiers/NotifierStub.spec";
+import { RegulatorStub } from "./regulation/RegulatorStub.spec";
+import { TotpHandlerStub } from "./authentication/totp/TotpHandlerStub.spec";
+import { UserDataStoreStub } from "./storage/UserDataStoreStub.spec";
+import { U2fHandlerStub } from "./authentication/u2f/U2fHandlerStub.spec";
+
+export interface ServerVariablesMock {
+  authorizer: AuthorizerStub;
+  config: Configuration;
+  usersDatabase: IUsersDatabaseStub;
+  logger: RequestLoggerStub;
+  notifier: NotifierStub;
+  regulator: RegulatorStub;
+  totpHandler: TotpHandlerStub;
+  userDataStore: UserDataStoreStub;
+  u2f: U2fHandlerStub;
+}
+
+export class ServerVariablesMockBuilder {
+  static build(enableLogging?: boolean): { variables: ServerVariables, mocks: ServerVariablesMock} {
+    const mocks: ServerVariablesMock = {
+      authorizer: new AuthorizerStub(),
+      config: {
+        access_control: {},
+        totp: {
+          issuer: "authelia.com"
+        },
+        authentication_backend: {
+          ldap: {
+            url: "ldap://ldap",
+            base_dn: "dc=example,dc=com",
+            user: "user",
+            password: "password",
+            mail_attribute: "mail",
+            additional_users_dn: "ou=users",
+            additional_groups_dn: "ou=groups",
+            users_filter: "cn={0}",
+            groups_filter: "member={dn}",
+            group_name_attribute: "cn"
+          },
+        },
+        logs_level: "debug",
+        notifier: {},
+        port: 8080,
+        regulation: {
+          ban_time: 50,
+          find_time: 50,
+          max_retries: 3
+        },
+        session: {
+          secret: "my_secret",
+          domain: "mydomain"
+        },
+        storage: {}
+      },
+      usersDatabase: new IUsersDatabaseStub(),
+      logger: new RequestLoggerStub(enableLogging),
+      notifier: new NotifierStub(),
+      regulator: new RegulatorStub(),
+      totpHandler: new TotpHandlerStub(),
+      userDataStore: new UserDataStoreStub(),
+      u2f: new U2fHandlerStub()
+    };
+    const vars: ServerVariables = {
+      authorizer: mocks.authorizer,
+      config: mocks.config,
+      usersDatabase: mocks.usersDatabase,
+      logger: mocks.logger,
+      notifier: mocks.notifier,
+      regulator: mocks.regulator,
+      totpHandler: mocks.totpHandler,
+      userDataStore: mocks.userDataStore,
+      u2f: mocks.u2f
+    };
+
+    return {
+      variables: vars,
+      mocks: mocks
+    };
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/Level.ts b/themes/squares/server/src/lib/authentication/Level.ts
new file mode 100644
index 00000000..57b6a234
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/Level.ts
@@ -0,0 +1,5 @@
+export enum Level {
+  NOT_AUTHENTICATED = 0,
+  ONE_FACTOR = 1,
+  TWO_FACTOR = 2
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/GroupsAndEmails.ts b/themes/squares/server/src/lib/authentication/backends/GroupsAndEmails.ts
new file mode 100644
index 00000000..3434ba66
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/GroupsAndEmails.ts
@@ -0,0 +1,5 @@
+
+export interface GroupsAndEmails {
+  groups: string[];
+  emails: string[];
+}
diff --git a/themes/squares/server/src/lib/authentication/backends/IUsersDatabase.ts b/themes/squares/server/src/lib/authentication/backends/IUsersDatabase.ts
new file mode 100644
index 00000000..d7fa13b7
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/IUsersDatabase.ts
@@ -0,0 +1,10 @@
+import Bluebird = require("bluebird");
+
+import { GroupsAndEmails } from "./GroupsAndEmails";
+
+export interface IUsersDatabase {
+  checkUserPassword(username: string, password: string): Bluebird<GroupsAndEmails>;
+  getEmails(username: string): Bluebird<string[]>;
+  getGroups(username: string): Bluebird<string[]>;
+  updatePassword(username: string, newPassword: string): Bluebird<void>;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/IUsersDatabaseStub.spec.ts b/themes/squares/server/src/lib/authentication/backends/IUsersDatabaseStub.spec.ts
new file mode 100644
index 00000000..19341a5d
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/IUsersDatabaseStub.spec.ts
@@ -0,0 +1,35 @@
+import Bluebird = require("bluebird");
+import Sinon = require("sinon");
+
+import { IUsersDatabase } from "./IUsersDatabase";
+import { GroupsAndEmails } from "./GroupsAndEmails";
+
+export class IUsersDatabaseStub implements IUsersDatabase {
+  checkUserPasswordStub: Sinon.SinonStub;
+  getEmailsStub: Sinon.SinonStub;
+  getGroupsStub: Sinon.SinonStub;
+  updatePasswordStub: Sinon.SinonStub;
+
+  constructor() {
+    this.checkUserPasswordStub = Sinon.stub();
+    this.getEmailsStub = Sinon.stub();
+    this.getGroupsStub = Sinon.stub();
+    this.updatePasswordStub = Sinon.stub();
+  }
+
+  checkUserPassword(username: string, password: string): Bluebird<GroupsAndEmails> {
+    return this.checkUserPasswordStub(username, password);
+  }
+
+  getEmails(username: string): Bluebird<string[]> {
+    return this.getEmailsStub(username);
+  }
+
+  getGroups(username: string): Bluebird<string[]> {
+    return this.getGroupsStub(username);
+  }
+
+  updatePassword(username: string, newPassword: string): Bluebird<void> {
+    return this.updatePasswordStub(username, newPassword);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/file/FileUsersDatabase.spec.ts b/themes/squares/server/src/lib/authentication/backends/file/FileUsersDatabase.spec.ts
new file mode 100644
index 00000000..a258a78f
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/file/FileUsersDatabase.spec.ts
@@ -0,0 +1,224 @@
+import Assert = require("assert");
+import Bluebird = require("bluebird");
+import Fs = require("fs");
+import Sinon = require("sinon");
+import Tmp = require("tmp");
+
+import { FileUsersDatabase } from "./FileUsersDatabase";
+import { FileUsersDatabaseConfiguration } from "../../../configuration/schema/FileUsersDatabaseConfiguration";
+import { HashGenerator } from "../../../utils/HashGenerator";
+
+const GOOD_DATABASE = `
+users:
+  john:
+    password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+    email: john.doe@authelia.com
+    groups:
+      - admins
+      - dev
+
+  harry:
+    password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+    emails: harry.potter@authelia.com
+    groups: []
+`;
+
+const BAD_HASH = `
+users:
+  john:
+    password: "{CRYPT}$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+    email: john.doe@authelia.com
+    groups:
+      - admins
+      - dev
+`;
+
+const NO_PASSWORD_DATABASE = `
+users:
+  john:
+    email: john.doe@authelia.com
+    groups:
+      - admins
+      - dev
+`;
+
+const NO_EMAIL_DATABASE = `
+users:
+  john:
+    password: "{CRYPT}$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+    groups:
+      - admins
+      - dev
+`;
+
+const SINGLE_USER_DATABASE = `
+users:
+  john:
+    password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+    email: john.doe@authelia.com
+    groups:
+      - admins
+      - dev
+`
+
+function createTmpFileFrom(yaml: string) {
+  const tmpFileAsync = Bluebird.promisify(Tmp.file);
+  return tmpFileAsync()
+    .then((path: string) => {
+      Fs.writeFileSync(path, yaml, "utf-8");
+      return Bluebird.resolve(path);
+    });
+}
+
+describe("authentication/backends/file/FileUsersDatabase", function() {
+  let configuration: FileUsersDatabaseConfiguration;
+
+  describe("checkUserPassword", () => {
+    describe("good config", () => {
+      beforeEach(() => {
+        return createTmpFileFrom(GOOD_DATABASE)
+          .then((path: string) => configuration = {
+            path: path
+          });
+      });
+
+      it("should succeed", () => {
+          const usersDatabase = new FileUsersDatabase(configuration);
+          return usersDatabase.checkUserPassword("john", "password")
+            .then((groupsAndEmails) => {
+              Assert.deepEqual(groupsAndEmails.groups, ["admins", "dev"]);
+              Assert.deepEqual(groupsAndEmails.emails, ["john.doe@authelia.com"]);
+            });
+      });
+
+      it("should fail when password is wrong", () => {
+          const usersDatabase = new FileUsersDatabase(configuration);
+          return usersDatabase.checkUserPassword("john", "bad_password")
+            .then(() => Bluebird.reject(new Error("should not be here.")))
+            .catch((err) => {
+              return Bluebird.resolve();
+            });
+      });
+
+      it("should fail when user does not exist", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.checkUserPassword("no_user", "password")
+          .then(() => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => {
+            return Bluebird.resolve();
+          });
+      });
+    });
+
+    describe("bad hash", () => {
+      beforeEach(() => {
+        return createTmpFileFrom(GOOD_DATABASE)
+          .then((path: string) => configuration = {
+            path: path
+          });
+      });
+
+      it("should fail when hash is wrong", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.checkUserPassword("john", "password")
+          .then(() => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => {
+            return Bluebird.resolve();
+          });
+      });
+    });
+
+    describe("no password", () => {
+      beforeEach(() => {
+        return createTmpFileFrom(NO_PASSWORD_DATABASE)
+          .then((path: string) => configuration = {
+            path: path
+          });
+      });
+
+      it("should fail", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.checkUserPassword("john", "password")
+          .then(() => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => {
+            return Bluebird.resolve();
+          });
+      });
+    });
+  });
+
+  describe("getEmails", () => {
+    describe("good config", () => {
+      beforeEach(() => {
+        return createTmpFileFrom(GOOD_DATABASE)
+          .then((path: string) => configuration = {
+            path: path
+          });
+      });
+
+      it("should succeed", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.getEmails("john")
+          .then((emails) => {
+            Assert.deepEqual(emails, ["john.doe@authelia.com"]);
+          });
+      });
+
+      it("should fail when user does not exist", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.getEmails("no_user")
+          .then(() => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => {
+            return Bluebird.resolve();
+          });
+      });
+    });
+
+    describe("no email provided", () => {
+      beforeEach(() => {
+        return createTmpFileFrom(NO_EMAIL_DATABASE)
+          .then((path: string) => configuration = {
+            path: path
+          });
+      });
+
+      it("should fail", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.getEmails("john")
+          .then(() => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => {
+            return Bluebird.resolve();
+          });
+      });
+    });
+  });
+
+  describe("updatePassword", () => {
+    beforeEach(() => {
+      return createTmpFileFrom(SINGLE_USER_DATABASE)
+        .then((path: string) => configuration = {
+          path: path
+        });
+    });
+
+    it("should succeed", () => {
+      const usersDatabase = new FileUsersDatabase(configuration);
+      const NEW_HASH = "{CRYPT}$6$rounds=500000$Qw6MhgADvLyYMEq9$ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+      const stub = Sinon.stub(HashGenerator, "ssha512").returns(Bluebird.resolve(NEW_HASH));
+      return usersDatabase.updatePassword("john", "mypassword")
+        .then(() => {
+          const content = Fs.readFileSync(configuration.path, "utf-8");
+          const matches = content.match(/password: '(.+)'/);
+          Assert.equal(matches[1], NEW_HASH);
+        })
+        .finally(() => stub.restore());
+    });
+
+    it("should fail when user does not exist", () => {
+      const usersDatabase = new FileUsersDatabase(configuration);
+      return usersDatabase.updatePassword("bad_user", "mypassword")
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch(() => Bluebird.resolve());
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/file/FileUsersDatabase.ts b/themes/squares/server/src/lib/authentication/backends/file/FileUsersDatabase.ts
new file mode 100644
index 00000000..d34dde21
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/file/FileUsersDatabase.ts
@@ -0,0 +1,182 @@
+import Bluebird = require("bluebird");
+import Fs = require("fs");
+import Yaml = require("yamljs");
+
+import { FileUsersDatabaseConfiguration }
+  from "../../../configuration/schema/FileUsersDatabaseConfiguration";
+import { GroupsAndEmails } from "../GroupsAndEmails";
+import { IUsersDatabase } from "../IUsersDatabase";
+import { HashGenerator } from "../../../utils/HashGenerator";
+import { ReadWriteQueue } from "./ReadWriteQueue";
+
+const loadAsync = Bluebird.promisify(Yaml.load);
+
+export class FileUsersDatabase implements IUsersDatabase {
+  private configuration: FileUsersDatabaseConfiguration;
+  private queue: ReadWriteQueue;
+
+  constructor(configuration: FileUsersDatabaseConfiguration) {
+    this.configuration = configuration;
+    this.queue = new ReadWriteQueue(this.configuration.path);
+  }
+
+  /**
+   * Read database from file.
+   * It enqueues the read task so that it is scheduled
+   * between other reads and writes.
+   */
+  private readDatabase(): Bluebird<any> {
+    return new Bluebird<string>((resolve, reject) => {
+      this.queue.read((err: Error, data: string) => {
+        if (err) {
+          reject(err);
+          return;
+        }
+        resolve(data);
+        this.queue.next();
+      });
+    })
+    .then((content) => {
+      const database = Yaml.parse(content);
+      if (!database) {
+        return Bluebird.reject(new Error("Unable to parse YAML file."));
+      }
+      return Bluebird.resolve(database);
+    });
+  }
+
+  /**
+   * Checks the user exists in the database.
+   */
+  private checkUserExists(
+    database: any,
+    username: string)
+    : Bluebird<void> {
+    if (!(username in database.users)) {
+      return Bluebird.reject(
+        new Error(`User ${username} does not exist in database.`));
+    }
+    return Bluebird.resolve();
+  }
+
+  /**
+   * Check the password of a given user.
+   */
+  private checkPassword(
+    database: any,
+    username: string,
+    password: string)
+    : Bluebird<void> {
+    const storedHash: string = database.users[username].password;
+    const matches = storedHash.match(/rounds=([0-9]+)\$([a-zA-z0-9]+)\$/);
+    if (!(matches && matches.length == 3)) {
+      return Bluebird.reject(new Error("Unable to detect the hash salt and rounds. " +
+        "Make sure the password is hashed with SSHA512."));
+    }
+
+    const rounds: number = parseInt(matches[1]);
+    const salt = matches[2];
+
+    return HashGenerator.ssha512(password, rounds, salt)
+      .then((hash: string) => {
+        if (hash !== storedHash) {
+          return Bluebird.reject(new Error("Wrong username/password."));
+        }
+        return Bluebird.resolve();
+      });
+  }
+
+  /**
+   * Retrieve email addresses of a given user.
+   */
+  private retrieveEmails(
+    database: any,
+    username: string)
+    : Bluebird<string[]> {
+    if (!("email" in database.users[username])) {
+      return Bluebird.reject(
+        new Error(`User ${username} has no email address.`));
+    }
+    return Bluebird.resolve(
+      [database.users[username].email]);
+  }
+
+  private retrieveGroups(
+    database: any,
+    username: string)
+    : Bluebird<string[]> {
+    if (!("groups" in database.users[username])) {
+      return Bluebird.resolve([]);
+    }
+    return Bluebird.resolve(
+      database.users[username].groups);
+  }
+
+  private replacePassword(
+    database: any,
+    username: string,
+    newPassword: string)
+    : Bluebird<void> {
+    const that = this;
+    return HashGenerator.ssha512(newPassword)
+      .then((hash) => {
+        database.users[username].password = hash;
+        const str = Yaml.stringify(database, 4, 2);
+        return Bluebird.resolve(str);
+      })
+      .then((content: string) => {
+        return new Bluebird((resolve, reject) => {
+          that.queue.write(content, (err) => {
+            if (err) {
+              return reject(err);
+            }
+            resolve();
+            that.queue.next();
+          });
+        });
+      });
+  }
+
+  checkUserPassword(
+    username: string,
+    password: string)
+    : Bluebird<GroupsAndEmails> {
+    return this.readDatabase()
+      .then((database) => {
+        return this.checkUserExists(database, username)
+          .then(() => this.checkPassword(database, username, password))
+          .then(() => {
+            return Bluebird.join(
+              this.retrieveEmails(database, username),
+              this.retrieveGroups(database, username)
+            ).spread((emails: string[], groups: string[]) => {
+              return { emails: emails, groups: groups };
+            });
+          });
+        });
+  }
+
+  getEmails(username: string): Bluebird<string[]> {
+    return this.readDatabase()
+      .then((database) => {
+        return this.checkUserExists(database, username)
+          .then(() => this.retrieveEmails(database, username));
+      });
+  }
+
+  getGroups(username: string): Bluebird<string[]> {
+    return this.readDatabase()
+      .then((database) => {
+        return this.checkUserExists(database, username)
+          .then(() => this.retrieveGroups(database, username));
+      });
+  }
+
+  updatePassword(username: string, newPassword: string): Bluebird<void> {
+    return this.readDatabase()
+      .then((database) => {
+        return this.checkUserExists(database, username)
+          .then(() => this.replacePassword(database, username, newPassword));
+      });
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/file/ReadWriteQueue.ts b/themes/squares/server/src/lib/authentication/backends/file/ReadWriteQueue.ts
new file mode 100644
index 00000000..957ddaec
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/file/ReadWriteQueue.ts
@@ -0,0 +1,60 @@
+import Fs = require("fs");
+
+type Callback = (err: Error, data?: string) => void;
+type ContentAndCallback = [string, Callback] | [string, string, Callback];
+
+/**
+ * WriteQueue is a queue synchronizing writes to a file.
+ *
+ * Example of use:
+ *
+ * queue.add(mycontent, (err) => {
+ *    // do whatever you want here.
+ *    queue.next();
+ * })
+ */
+export class ReadWriteQueue {
+  private filePath: string;
+  private queue: ContentAndCallback[];
+
+  constructor (filePath: string) {
+    this.queue = [];
+    this.filePath = filePath;
+  }
+
+  next () {
+    if (this.queue.length === 0)
+      return;
+
+    const task = this.queue[0];
+
+    if (task[0] == "write") {
+      Fs.writeFile(this.filePath, task[1], "utf-8", (err) => {
+        this.queue.shift();
+        const cb = task[2] as Callback;
+        cb(err);
+      });
+    }
+    else if (task[0] == "read") {
+      Fs.readFile(this.filePath, { encoding: "utf-8"} , (err, data) => {
+        this.queue.shift();
+        const cb = task[1] as Callback;
+        cb(err, data);
+      });
+    }
+  }
+
+  write (content: string, cb: Callback) {
+    this.queue.push(["write", content, cb]);
+    if (this.queue.length === 1) {
+      this.next();
+    }
+  }
+
+  read (cb: Callback) {
+    this.queue.push(["read", cb]);
+    if (this.queue.length === 1) {
+      this.next();
+    }
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/ISession.ts b/themes/squares/server/src/lib/authentication/backends/ldap/ISession.ts
new file mode 100644
index 00000000..da2c7443
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/ISession.ts
@@ -0,0 +1,12 @@
+
+import BluebirdPromise = require("bluebird");
+
+export interface ISession {
+  open(): BluebirdPromise<void>;
+  close(): BluebirdPromise<void>;
+
+  searchUserDn(username: string): BluebirdPromise<string>;
+  searchEmails(username: string): BluebirdPromise<string[]>;
+  searchGroups(username: string): BluebirdPromise<string[]>;
+  modifyPassword(username: string, newPassword: string): BluebirdPromise<void>;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/ISessionFactory.ts b/themes/squares/server/src/lib/authentication/backends/ldap/ISessionFactory.ts
new file mode 100644
index 00000000..014d1eea
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/ISessionFactory.ts
@@ -0,0 +1,6 @@
+
+import { ISession } from "./ISession";
+
+export interface ISessionFactory {
+  create(userDN: string, password: string): ISession;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.spec.ts b/themes/squares/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.spec.ts
new file mode 100644
index 00000000..f4a6e630
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.spec.ts
@@ -0,0 +1,386 @@
+import Assert = require("assert");
+import Bluebird = require("bluebird");
+
+import { LdapUsersDatabase } from "./LdapUsersDatabase";
+
+import { SessionFactoryStub } from "./SessionFactoryStub.spec";
+import { SessionStub } from "./SessionStub.spec";
+
+const ADMIN_USER_DN = "cn=admin,dc=example,dc=com";
+const ADMIN_PASSWORD = "password";
+
+describe("ldap/connector/LdapUsersDatabase", function() {
+  let sessionFactory: SessionFactoryStub;
+  let usersDatabase: LdapUsersDatabase;
+
+  const USERNAME = "user";
+  const PASSWORD = "pass";
+  const NEW_PASSWORD = "pass2";
+
+  const LDAP_CONFIG = {
+    url: "http://localhost:324",
+    additional_users_dn: "ou=users",
+    additional_groups_dn: "ou=groups",
+    base_dn: "dc=example,dc=com",
+    users_filter: "cn={0}",
+    groups_filter: "member={0}",
+    mail_attribute: "mail",
+    group_name_attribute: "cn",
+    user: ADMIN_USER_DN,
+    password: ADMIN_PASSWORD
+  };
+
+  beforeEach(function() {
+    sessionFactory = new SessionFactoryStub();
+    usersDatabase = new LdapUsersDatabase(sessionFactory, LDAP_CONFIG);
+  })
+
+  describe("checkUserPassword", function() {
+    it("should return groups and emails when user/password matches", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+      const emails = ["email1", "email2"];
+      const groups = ["group1", "group2"];
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.resolve());
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
+      adminSession.searchEmailsStub.withArgs(USERNAME).returns(Bluebird.resolve(emails));
+      adminSession.searchGroupsStub.withArgs(USERNAME).returns(Bluebird.resolve(groups));
+      
+      userSession.openStub.returns(Bluebird.resolve());
+      userSession.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then((groupsAndEmails) => {
+          Assert.deepEqual(groupsAndEmails.groups, groups);
+          Assert.deepEqual(groupsAndEmails.emails, emails);
+        })
+    });
+
+    it("should fail when username/password is wrong", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.resolve());
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
+      
+      userSession.openStub.returns(Bluebird.reject(new Error("Failed binding")));
+      userSession.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(userSession.closeStub.called);
+          Assert(adminSession.closeStub.called);
+          return Bluebird.resolve();
+        })
+    });
+
+    it("should fail when admin binding fails", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.reject(new Error("Failed binding")));
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(userSession.closeStub.notCalled);
+          Assert(adminSession.closeStub.called);
+          return Bluebird.resolve();
+        })
+    });
+
+    it("should fail when search for user dn fails", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.resolve());
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.reject(new Error("Failed searching user dn")));
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(userSession.closeStub.notCalled);
+          Assert(adminSession.closeStub.called);
+          return Bluebird.resolve();
+        })
+    });
+
+    it("should fail when groups retrieval fails", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+      const emails = ["email1", "email2"];
+      const groups = ["group1", "group2"];
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.resolve());
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
+      adminSession.searchEmailsStub.withArgs(USERNAME)
+        .returns(Bluebird.resolve(emails));
+      adminSession.searchGroupsStub.withArgs(USERNAME)
+        .returns(Bluebird.reject(new Error("Failed retrieving groups")));
+      
+      userSession.openStub.returns(Bluebird.resolve());
+      userSession.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then((groupsAndEmails) => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(userSession.closeStub.called);
+          Assert(adminSession.closeStub.called);
+        })
+    });
+
+    it("should fail when emails retrieval fails", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+      const emails = ["email1", "email2"];
+      const groups = ["group1", "group2"];
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.resolve());
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
+      adminSession.searchEmailsStub.withArgs(USERNAME)
+        .returns(Bluebird.reject(new Error("Emails retrieval failed")));
+      adminSession.searchGroupsStub.withArgs(USERNAME)
+        .returns(Bluebird.resolve(groups));
+      
+      userSession.openStub.returns(Bluebird.resolve());
+      userSession.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then((groupsAndEmails) => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(userSession.closeStub.called);
+          Assert(adminSession.closeStub.called);
+        })
+    });
+  });
+
+  describe("getEmails", function() {
+    it("should succefully retrieves email", () => {
+      const emails = ["email1", "email2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.closeStub.returns(Bluebird.resolve());
+      session.searchEmailsStub.returns(Bluebird.resolve(emails));
+
+      return usersDatabase.getEmails(USERNAME)
+        .then((foundEmails) => {
+          Assert(session.closeStub.called);
+          Assert.deepEqual(foundEmails, emails);
+        })
+    });
+
+    it("should fail when binding fails", () => {
+      const emails = ["email1", "email2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.reject(new Error("Binding failed")));
+
+      return usersDatabase.getEmails(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when unbinding fails", () => {
+      const emails = ["email1", "email2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.searchEmailsStub.returns(Bluebird.resolve(emails));
+      session.closeStub.returns(Bluebird.reject(new Error("Unbinding failed")));
+
+      return usersDatabase.getEmails(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when search fails", () => {
+      const emails = ["email1", "email2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.searchEmailsStub.returns(Bluebird.reject(new Error("Search failed")));
+      session.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.getEmails(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+  });
+
+
+  describe("getGroups", function() {
+    it("should succefully retrieves groups", () => {
+      const groups = ["group1", "group2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.closeStub.returns(Bluebird.resolve());
+      session.searchGroupsStub.returns(Bluebird.resolve(groups));
+
+      return usersDatabase.getGroups(USERNAME)
+        .then((foundGroups) => {
+          Assert(session.closeStub.called);
+          Assert.deepEqual(foundGroups, groups);
+        })
+    });
+
+    it("should fail when binding fails", () => {
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.reject(new Error("Binding failed")));
+
+      return usersDatabase.getGroups(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when unbinding fails", () => {
+      const groups = ["group1", "group2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.searchGroupsStub.returns(Bluebird.resolve(groups));
+      session.closeStub.returns(Bluebird.reject(new Error("Unbinding failed")));
+
+      return usersDatabase.getGroups(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when search fails", () => {
+      const groups = ["group1", "group2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.searchGroupsStub.returns(Bluebird.reject(new Error("Search failed")));
+      session.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.getGroups(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+  });
+
+
+  describe("updatePassword", function() {
+    it("should successfully update password", () => {
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.closeStub.returns(Bluebird.resolve());
+      session.modifyPasswordStub.returns(Bluebird.resolve());
+
+      return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
+        .then(() => {
+          Assert(session.modifyPasswordStub.calledWith(USERNAME, NEW_PASSWORD));
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when binding fails", () => {
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.reject(new Error("Binding failed")));
+      session.closeStub.returns(Bluebird.resolve());
+      session.modifyPasswordStub.returns(Bluebird.resolve());
+
+      return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch(() => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when update fails", () => {
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.closeStub.returns(Bluebird.reject(new Error("Update failed")));
+      session.modifyPasswordStub.returns(Bluebird.resolve());
+
+      return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch(() => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when unbind fails", () => {
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.closeStub.returns(Bluebird.resolve());
+      session.modifyPasswordStub.returns(Bluebird.reject(new Error("Unbind failed")));
+
+      return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch(() => {
+          Assert(session.closeStub.called);
+        })
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.ts b/themes/squares/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.ts
new file mode 100644
index 00000000..edda62ec
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.ts
@@ -0,0 +1,107 @@
+import Bluebird = require("bluebird");
+import { IUsersDatabase } from "../IUsersDatabase";
+import { ISessionFactory } from "./ISessionFactory";
+import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
+import { ISession } from "./ISession";
+import { GroupsAndEmails } from "../GroupsAndEmails";
+import Exceptions = require("../../../Exceptions");
+
+type SessionCallback<T> = (session: ISession) => Bluebird<T>;
+
+export class LdapUsersDatabase implements IUsersDatabase {
+  private sessionFactory: ISessionFactory;
+  private configuration: LdapConfiguration;
+
+  constructor(
+    sessionFactory: ISessionFactory,
+    configuration: LdapConfiguration) {
+    this.sessionFactory = sessionFactory;
+    this.configuration = configuration;
+  }
+
+  private withSession<T>(
+    username: string,
+    password: string,
+    cb: SessionCallback<T>): Bluebird<T> {
+    const session = this.sessionFactory.create(username, password);
+    return session.open()
+      .then(() => cb(session))
+      .finally(() => session.close());
+  }
+
+  checkUserPassword(username: string, password: string): Bluebird<GroupsAndEmails> {
+    const that = this;
+    function verifyUserPassword(userDN: string) {
+      return that.withSession<void>(
+        userDN,
+        password,
+        (session) => Bluebird.resolve()
+      );
+    }
+
+    function getInfo(session: ISession) {
+        return Bluebird.join(
+          session.searchGroups(username),
+          session.searchEmails(username)
+        )
+        .spread((groups: string[], emails: string[]) => {
+          return { groups: groups, emails: emails };
+        });
+    }
+
+    return that.withSession(
+      that.configuration.user,
+      that.configuration.password,
+      (session) => {
+        return session.searchUserDn(username)
+          .then(verifyUserPassword)
+          .then(() => getInfo(session));
+      })
+      .catch((err) =>
+        Bluebird.reject(new Exceptions.LdapError(err.message)));
+  }
+
+  getEmails(username: string): Bluebird<string[]> {
+    const that = this;
+    return that.withSession(
+      that.configuration.user,
+      that.configuration.password,
+      (session) => {
+        return session.searchEmails(username);
+      }
+    )
+    .catch((err) =>
+      Bluebird.reject(new Exceptions.LdapError("Failed during email retrieval: " + err.message))
+    );
+  }
+
+  getGroups(username: string): Bluebird<string[]> {
+    const that = this;
+    return that.withSession(
+      that.configuration.user,
+      that.configuration.password,
+      (session) => {
+        return session.searchGroups(username);
+      }
+    )
+    .catch((err) =>
+      Bluebird.reject(new Exceptions.LdapError("Failed during email retrieval: " + err.message))
+    );
+  }
+
+  updatePassword(username: string, newPassword: string): Bluebird<void> {
+    const that = this;
+    return that.withSession(
+      that.configuration.user,
+      that.configuration.password,
+      (session) => {
+        return session.modifyPassword(username, newPassword);
+      }
+    )
+    .catch(function (err: Error) {
+      return Bluebird.reject(
+        new Exceptions.LdapError(
+          "Error while updating password: " + err.message));
+    });
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/SafeSession.spec.ts b/themes/squares/server/src/lib/authentication/backends/ldap/SafeSession.spec.ts
new file mode 100644
index 00000000..9dedfcb7
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/SafeSession.spec.ts
@@ -0,0 +1,76 @@
+import BluebirdPromise = require("bluebird");
+import { SessionStub } from "./SessionStub.spec";
+import { SafeSession } from "./SafeSession";
+
+describe("ldap/SanitizedClient", function () {
+  let client: SafeSession;
+
+  beforeEach(function () {
+    const clientStub = new SessionStub();
+    clientStub.searchUserDnStub.onCall(0).returns(BluebirdPromise.resolve());
+    clientStub.searchGroupsStub.onCall(0).returns(BluebirdPromise.resolve());
+    clientStub.searchEmailsStub.onCall(0).returns(BluebirdPromise.resolve());
+    clientStub.modifyPasswordStub.onCall(0).returns(BluebirdPromise.resolve());
+    client = new SafeSession(clientStub);
+  });
+
+  describe("special chars are used", function () {
+    it("should fail when special chars are used in searchUserDn", function () {
+      // potential ldap injection";
+      return client.searchUserDn("cn=dummy_user,ou=groupgs")
+        .then(function () {
+          return BluebirdPromise.reject(new Error("Should not be here."));
+        }, function () {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail when special chars are used in searchGroups", function () {
+      // potential ldap injection";
+      return client.searchGroups("cn=dummy_user,ou=groupgs")
+        .then(function () {
+          return BluebirdPromise.reject(new Error("Should not be here."));
+        }, function () {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail when special chars are used in searchEmails", function () {
+      // potential ldap injection";
+      return client.searchEmails("cn=dummy_user,ou=groupgs")
+        .then(function () {
+          return BluebirdPromise.reject(new Error("Should not be here."));
+        }, function () {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail when special chars are used in modifyPassword", function () {
+      // potential ldap injection";
+      return client.modifyPassword("cn=dummy_user,ou=groupgs", "abc")
+        .then(function () {
+          return BluebirdPromise.reject(new Error("Should not be here."));
+        }, function () {
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+
+  describe("no special chars are used", function() {
+    it("should succeed when no special chars are used in searchUserDn", function () {
+      return client.searchUserDn("dummy_user");
+    });
+
+    it("should succeed when no special chars are used in searchGroups", function () {
+      return client.searchGroups("dummy_user");
+    });
+
+    it("should succeed when no special chars are used in searchEmails", function () {
+      return client.searchEmails("dummy_user");
+    });
+
+    it("should succeed when no special chars are used in modifyPassword", function () {
+      return client.modifyPassword("dummy_user", "abc");
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/SafeSession.ts b/themes/squares/server/src/lib/authentication/backends/ldap/SafeSession.ts
new file mode 100644
index 00000000..57220906
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/SafeSession.ts
@@ -0,0 +1,62 @@
+import BluebirdPromise = require("bluebird");
+import { ISession } from "./ISession";
+import { Sanitizer } from "./Sanitizer";
+
+const SPECIAL_CHAR_USED_MESSAGE = "Special character used in LDAP query.";
+
+
+export class SafeSession implements ISession {
+  private sesion: ISession;
+
+  constructor(sesion: ISession) {
+    this.sesion = sesion;
+  }
+
+  open(): BluebirdPromise<void> {
+    return this.sesion.open();
+  }
+
+  close(): BluebirdPromise<void> {
+    return this.sesion.close();
+  }
+
+  searchGroups(username: string): BluebirdPromise<string[]> {
+    try {
+      const sanitizedUsername = Sanitizer.sanitize(username);
+      return this.sesion.searchGroups(sanitizedUsername);
+    }
+    catch (e) {
+      return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
+    }
+  }
+
+  searchUserDn(username: string): BluebirdPromise<string> {
+    try {
+      const sanitizedUsername = Sanitizer.sanitize(username);
+      return this.sesion.searchUserDn(sanitizedUsername);
+    }
+    catch (e) {
+      return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
+    }
+  }
+
+  searchEmails(username: string): BluebirdPromise<string[]> {
+    try {
+      const sanitizedUsername = Sanitizer.sanitize(username);
+      return this.sesion.searchEmails(sanitizedUsername);
+    }
+    catch (e) {
+      return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
+    }
+  }
+
+  modifyPassword(username: string, newPassword: string): BluebirdPromise<void> {
+    try {
+      const sanitizedUsername = Sanitizer.sanitize(username);
+      return this.sesion.modifyPassword(sanitizedUsername, newPassword);
+    }
+    catch (e) {
+      return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
+    }
+  }
+}
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/Sanitizer.spec.ts b/themes/squares/server/src/lib/authentication/backends/ldap/Sanitizer.spec.ts
new file mode 100644
index 00000000..9dd33fed
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/Sanitizer.spec.ts
@@ -0,0 +1,25 @@
+import Assert = require("assert");
+import { Sanitizer } from "./Sanitizer";
+
+describe("ldap/InputsSanitizer", function () {
+  it("should fail when special characters are used", function () {
+    Assert.throws(() => { Sanitizer.sanitize("ab,c"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a\\bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a'bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a#bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a+bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a<bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a>bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a;bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a\"bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a=bc"); }, Error);
+  });
+
+  it("should return original string", function () {
+    Assert.equal(Sanitizer.sanitize("abcdef"), "abcdef");
+  });
+
+  it("should trim", function () {
+    Assert.throws(() => { Sanitizer.sanitize("    abc    "); }, Error);
+  });
+});
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/Sanitizer.ts b/themes/squares/server/src/lib/authentication/backends/ldap/Sanitizer.ts
new file mode 100644
index 00000000..be74132a
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/Sanitizer.ts
@@ -0,0 +1,25 @@
+
+// returns true for 1 or more matches, where 'a' is an array and 'b' is a search string or an array of multiple search strings
+function contains(a: string, character: string) {
+  // string match
+  return a.indexOf(character) > -1;
+}
+
+function containsOneOf(s: string, characters: string[]) {
+  return characters
+    .map((character: string) => { return contains(s, character); })
+    .reduce((acc: boolean, current: boolean) => { return acc || current; }, false);
+}
+
+export class Sanitizer {
+  static sanitize(input: string): string {
+    const forbiddenChars = [",", "\\", "'", "#", "+", "<", ">", ";", "\"", "="];
+    if (containsOneOf(input, forbiddenChars))
+      throw new Error("Input containing unsafe characters.");
+
+    if (input != input.trim())
+      throw new Error("Input has unexpected spaces.");
+
+    return input;
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/Session.spec.ts b/themes/squares/server/src/lib/authentication/backends/ldap/Session.spec.ts
new file mode 100644
index 00000000..d55f6a80
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/Session.spec.ts
@@ -0,0 +1,127 @@
+
+import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
+import { Session } from "./Session";
+import { ConnectorFactoryStub } from "./connector/ConnectorFactoryStub.spec";
+import { ConnectorStub } from "./connector/ConnectorStub.spec";
+
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+import Winston = require("winston");
+
+describe("ldap/Session", function () {
+  const USERNAME = "username";
+  const ADMIN_USER_DN = "cn=admin,dc=example,dc=com";
+  const ADMIN_PASSWORD = "password";
+
+  it("should replace {0} by username when searching for groups in LDAP", function () {
+    const options: LdapConfiguration = {
+      url: "ldap://ldap",
+      additional_users_dn: "ou=users",
+      additional_groups_dn: "ou=groups",
+      base_dn: "dc=example,dc=com",
+      users_filter: "cn={0}",
+      groups_filter: "member=cn={0},ou=users,dc=example,dc=com",
+      group_name_attribute: "cn",
+      mail_attribute: "mail",
+      user: "cn=admin,dc=example,dc=com",
+      password: "password"
+    };
+    const connectorStub = new ConnectorStub();
+    connectorStub.searchAsyncStub.returns(BluebirdPromise.resolve([{
+      cn: "group1"
+    }]));
+    const client = new Session(ADMIN_USER_DN, ADMIN_PASSWORD, options, connectorStub, Winston);
+
+    return client.searchGroups("user1")
+      .then(function () {
+        Assert.equal(connectorStub.searchAsyncStub.getCall(0).args[1].filter,
+          "member=cn=user1,ou=users,dc=example,dc=com");
+      });
+  });
+
+  it("should replace {dn} by user DN when searching for groups in LDAP", function () {
+    const USER_DN = "cn=user1,ou=users,dc=example,dc=com";
+    const options: LdapConfiguration = {
+      url: "ldap://ldap",
+      additional_users_dn: "ou=users",
+      additional_groups_dn: "ou=groups",
+      base_dn: "dc=example,dc=com",
+      users_filter: "cn={0}",
+      groups_filter: "member={dn}",
+      group_name_attribute: "cn",
+      mail_attribute: "mail",
+      user: "cn=admin,dc=example,dc=com",
+      password: "password"
+    };
+    const ldapClient = new ConnectorStub();
+
+    // Retrieve user DN
+    ldapClient.searchAsyncStub.withArgs("ou=users,dc=example,dc=com", {
+      scope: "sub",
+      sizeLimit: 1,
+      attributes: ["dn"],
+      filter: "cn=user1"
+    }).returns(BluebirdPromise.resolve([{
+      dn: USER_DN
+    }]));
+
+    // Retrieve groups
+    ldapClient.searchAsyncStub.withArgs("ou=groups,dc=example,dc=com", {
+      scope: "sub",
+      attributes: ["cn"],
+      filter: "member=" + USER_DN
+    }).returns(BluebirdPromise.resolve([{
+      cn: "group1"
+    }]));
+
+    const client = new Session(ADMIN_USER_DN, ADMIN_PASSWORD, options, ldapClient, Winston);
+
+    return client.searchGroups("user1")
+      .then(function (groups: string[]) {
+        Assert.deepEqual(groups, ["group1"]);
+      });
+  });
+
+  it("should retrieve mail from custom attribute", function () {
+    const USER_DN = "cn=user1,ou=users,dc=example,dc=com";
+    const options: LdapConfiguration = {
+      url: "ldap://ldap",
+      additional_users_dn: "ou=users",
+      additional_groups_dn: "ou=groups",
+      base_dn: "dc=example,dc=com",
+      users_filter: "cn={0}",
+      groups_filter: "member={dn}",
+      group_name_attribute: "cn",
+      mail_attribute: "custom_mail",
+      user: "cn=admin,dc=example,dc=com",
+      password: "password"
+    };
+    const connector = new ConnectorStub();
+    // Retrieve user DN
+    connector.searchAsyncStub.withArgs("ou=users,dc=example,dc=com", {
+      scope: "sub",
+      sizeLimit: 1,
+      attributes: ["dn"],
+      filter: "cn=user1"
+    }).returns(BluebirdPromise.resolve([{
+      dn: USER_DN
+    }]));
+
+    // Retrieve email
+    connector.searchAsyncStub.withArgs("cn=user1,ou=users,dc=example,dc=com", {
+      scope: "base",
+      sizeLimit: 1,
+      attributes: ["custom_mail"],
+    }).returns(BluebirdPromise.resolve([{
+      custom_mail: "user1@example.com"
+    }]));
+
+    const client = new Session(ADMIN_USER_DN, ADMIN_PASSWORD, options, connector, Winston);
+
+    return client.searchEmails("user1")
+      .then(function (emails: string[]) {
+        Assert.deepEqual(emails, ["user1@example.com"]);
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/Session.ts b/themes/squares/server/src/lib/authentication/backends/ldap/Session.ts
new file mode 100644
index 00000000..e0284b3c
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/Session.ts
@@ -0,0 +1,156 @@
+import BluebirdPromise = require("bluebird");
+import exceptions = require("../../../Exceptions");
+import { EventEmitter } from "events";
+import { ISession } from "./ISession";
+import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
+import { Winston } from "../../../../../types/Dependencies";
+import Util = require("util");
+import { HashGenerator } from "../../../utils/HashGenerator";
+import { IConnector } from "./connector/IConnector";
+
+export class Session implements ISession {
+  private userDN: string;
+  private password: string;
+  private connector: IConnector;
+  private logger: Winston;
+  private options: LdapConfiguration;
+
+  private groupsSearchBase: string;
+  private usersSearchBase: string;
+
+  constructor(userDN: string, password: string, options: LdapConfiguration,
+    connector: IConnector, logger: Winston) {
+    this.options = options;
+    this.logger = logger;
+    this.userDN = userDN;
+    this.password = password;
+    this.connector = connector;
+
+    this.groupsSearchBase = (this.options.additional_groups_dn)
+      ? Util.format("%s,%s", this.options.additional_groups_dn, this.options.base_dn)
+      : this.options.base_dn;
+
+    this.usersSearchBase = (this.options.additional_users_dn)
+      ? Util.format("%s,%s", this.options.additional_users_dn, this.options.base_dn)
+      : this.options.base_dn;
+  }
+
+  open(): BluebirdPromise<void> {
+    this.logger.debug("LDAP: Bind user '%s'", this.userDN);
+    return this.connector.bindAsync(this.userDN, this.password)
+      .error(function (err: Error) {
+        return BluebirdPromise.reject(new exceptions.LdapBindError(err.message));
+      });
+  }
+
+  close(): BluebirdPromise<void> {
+    this.logger.debug("LDAP: Unbind user '%s'", this.userDN);
+    return this.connector.unbindAsync()
+      .error(function (err: Error) {
+        return BluebirdPromise.reject(new exceptions.LdapBindError(err.message));
+      });
+  }
+
+  private createGroupsFilter(userGroupsFilter: string, username: string): BluebirdPromise<string> {
+    if (userGroupsFilter.indexOf("{0}") > 0) {
+      return BluebirdPromise.resolve(userGroupsFilter.replace("{0}", username));
+    }
+    else if (userGroupsFilter.indexOf("{dn}") > 0) {
+      return this.searchUserDn(username)
+        .then(function (userDN: string) {
+          return BluebirdPromise.resolve(userGroupsFilter.replace("{dn}", userDN));
+        });
+    }
+    return BluebirdPromise.resolve(userGroupsFilter);
+  }
+
+  searchGroups(username: string): BluebirdPromise<string[]> {
+    const that = this;
+    return this.createGroupsFilter(this.options.groups_filter, username)
+      .then(function (groupsFilter: string) {
+        that.logger.debug("Computed groups filter is %s", groupsFilter);
+        const query = {
+          scope: "sub",
+          attributes: [that.options.group_name_attribute],
+          filter: groupsFilter
+        };
+        return that.connector.searchAsync(that.groupsSearchBase, query);
+      })
+      .then(function (docs: { cn: string }[]) {
+        const groups = docs.map((doc: any) => { return doc.cn; });
+        that.logger.debug("LDAP: groups of user %s are [%s]", username, groups.join(","));
+        return BluebirdPromise.resolve(groups);
+      });
+  }
+
+  searchUserDn(username: string): BluebirdPromise<string> {
+    const that = this;
+    const filter = this.options.users_filter.replace("{0}", username);
+    this.logger.debug("Computed users filter is %s", filter);
+    const query = {
+      scope: "sub",
+      sizeLimit: 1,
+      attributes: ["dn"],
+      filter: filter
+    };
+
+    that.logger.debug("LDAP: searching for user dn of %s", username);
+    return that.connector.searchAsync(this.usersSearchBase, query)
+      .then(function (users: { dn: string }[]) {
+        if (users.length > 0) {
+          that.logger.debug("LDAP: retrieved user dn is %s", users[0].dn);
+          return BluebirdPromise.resolve(users[0].dn);
+        }
+        return BluebirdPromise.reject(new Error(
+          Util.format("No user DN found for user '%s'", username)));
+      });
+  }
+
+  searchEmails(username: string): BluebirdPromise<string[]> {
+    const that = this;
+    const query = {
+      scope: "base",
+      sizeLimit: 1,
+      attributes: [this.options.mail_attribute]
+    };
+
+    return this.searchUserDn(username)
+      .then(function (userDN) {
+        return that.connector.searchAsync(userDN, query);
+      })
+      .then(function (docs: { [mail_attribute: string]: string }[]) {
+        const emails: string[] = docs
+          .filter((d) => { return typeof d[that.options.mail_attribute] === "string"; })
+          .map((d) => { return d[that.options.mail_attribute]; });
+        that.logger.debug("LDAP: emails of user '%s' are %s", username, emails);
+        return BluebirdPromise.resolve(emails);
+      })
+      .catch(function (err: Error) {
+        return BluebirdPromise.reject(new exceptions.LdapError("Error while searching emails. " + err.stack));
+      });
+  }
+
+  modifyPassword(username: string, newPassword: string): BluebirdPromise<void> {
+    const that = this;
+    this.logger.debug("LDAP: update password of user '%s'", username);
+    return this.searchUserDn(username)
+      .then(function (userDN: string) {
+        return BluebirdPromise.join(
+          HashGenerator.ssha512(newPassword),
+          BluebirdPromise.resolve(userDN));
+      })
+      .then(function (res: string[]) {
+        const change = {
+          operation: "replace",
+          modification: {
+            userPassword: res[0]
+          }
+        };
+        that.logger.debug("Password new='%s'", change.modification.userPassword);
+        return that.connector.modifyAsync(res[1], change);
+      })
+      .then(function () {
+        return that.connector.unbindAsync();
+      });
+  }
+}
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/SessionFactory.ts b/themes/squares/server/src/lib/authentication/backends/ldap/SessionFactory.ts
new file mode 100644
index 00000000..0b6c4bff
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/SessionFactory.ts
@@ -0,0 +1,37 @@
+import Ldapjs = require("ldapjs");
+import Winston = require("winston");
+
+import { IConnectorFactory } from "./connector/IConnectorFactory";
+import { ISessionFactory } from "./ISessionFactory";
+import { ISession } from "./ISession";
+import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
+import { Session } from "./Session";
+import { SafeSession } from "./SafeSession";
+
+
+export class SessionFactory implements ISessionFactory {
+  private config: LdapConfiguration;
+  private connectorFactory: IConnectorFactory;
+  private logger: typeof Winston;
+
+  constructor(ldapConfiguration: LdapConfiguration,
+    connectorFactory: IConnectorFactory,
+    logger: typeof Winston) {
+    this.config = ldapConfiguration;
+    this.connectorFactory = connectorFactory;
+    this.logger = logger;
+  }
+
+  create(userDN: string, password: string): ISession {
+    const connector = this.connectorFactory.create();
+    return new SafeSession(
+      new Session(
+        userDN,
+        password,
+        this.config,
+        connector,
+        this.logger
+      )
+    );
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/SessionFactoryStub.spec.ts b/themes/squares/server/src/lib/authentication/backends/ldap/SessionFactoryStub.spec.ts
new file mode 100644
index 00000000..face3930
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/SessionFactoryStub.spec.ts
@@ -0,0 +1,16 @@
+import Sinon = require("sinon");
+
+import { ISession } from "./ISession";
+import { ISessionFactory } from "./ISessionFactory";
+
+export class SessionFactoryStub implements ISessionFactory {
+  createStub: Sinon.SinonStub;
+
+  constructor() {
+    this.createStub = Sinon.stub();
+  }
+
+  create(userDN: string, password: string): ISession {
+    return this.createStub(userDN, password);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/SessionStub.spec.ts b/themes/squares/server/src/lib/authentication/backends/ldap/SessionStub.spec.ts
new file mode 100644
index 00000000..5faf2ba1
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/SessionStub.spec.ts
@@ -0,0 +1,46 @@
+import Bluebird = require("bluebird");
+import Sinon = require("sinon");
+
+import { ISession } from "./ISession";
+
+export class SessionStub implements ISession {
+  openStub: Sinon.SinonStub;
+  closeStub: Sinon.SinonStub;
+  searchUserDnStub: Sinon.SinonStub;
+  searchEmailsStub: Sinon.SinonStub;
+  searchGroupsStub: Sinon.SinonStub;
+  modifyPasswordStub: Sinon.SinonStub;
+
+  constructor() {
+    this.openStub = Sinon.stub();
+    this.closeStub = Sinon.stub();
+    this.searchUserDnStub = Sinon.stub();
+    this.searchEmailsStub = Sinon.stub();
+    this.searchGroupsStub = Sinon.stub();
+    this.modifyPasswordStub = Sinon.stub();
+  }
+
+  open(): Bluebird<void> {
+    return this.openStub();
+  }
+
+  close(): Bluebird<void> {
+    return this.closeStub();
+  }
+
+  searchUserDn(username: string): Bluebird<string> {
+    return this.searchUserDnStub(username);
+  }
+
+  searchEmails(username: string): Bluebird<string[]> {
+    return this.searchEmailsStub(username);
+  }
+
+  searchGroups(username: string): Bluebird<string[]> {
+    return this.searchGroupsStub(username);
+  }
+
+  modifyPassword(username: string, newPassword: string): Bluebird<void> {
+    return this.modifyPasswordStub(username, newPassword);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/connector/Connector.ts b/themes/squares/server/src/lib/authentication/backends/ldap/connector/Connector.ts
new file mode 100644
index 00000000..2542ea7f
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/connector/Connector.ts
@@ -0,0 +1,69 @@
+import LdapJs = require("ldapjs");
+import EventEmitter = require("events");
+import Bluebird = require("bluebird");
+import { IConnector } from "./IConnector";
+import Exceptions = require("../../../../Exceptions");
+
+interface SearchEntry {
+  object: any;
+}
+
+export interface ClientAsync {
+  on(event: string, callback: (data?: any) => void): void;
+  bindAsync(username: string, password: string): Bluebird<void>;
+  unbindAsync(): Bluebird<void>;
+  searchAsync(base: string, query: LdapJs.SearchOptions): Bluebird<EventEmitter>;
+  modifyAsync(userdn: string, change: LdapJs.Change): Bluebird<void>;
+}
+
+export class Connector implements IConnector {
+  private client: ClientAsync;
+
+  constructor(url: string, ldapjs: typeof LdapJs) {
+    const ldapClient = ldapjs.createClient({
+      url: url,
+      reconnect: true
+    });
+
+    /*const clientLogger = (ldapClient as any).log;
+    if (clientLogger) {
+      clientLogger.level("trace");
+    }*/
+
+    this.client = Bluebird.promisifyAll(ldapClient) as any;
+  }
+
+  bindAsync(username: string, password: string): Bluebird<void> {
+    return this.client.bindAsync(username, password);
+  }
+
+  unbindAsync(): Bluebird<void> {
+    return this.client.unbindAsync();
+  }
+
+  searchAsync(base: string, query: any): Bluebird<any[]> {
+    const that = this;
+    return this.client.searchAsync(base, query)
+      .then(function (res: EventEmitter) {
+        const doc: SearchEntry[] = [];
+        return new Bluebird<any[]>((resolve, reject) => {
+          res.on("searchEntry", function (entry: SearchEntry) {
+            doc.push(entry.object);
+          });
+          res.on("error", function (err: Error) {
+            reject(new Exceptions.LdapSearchError(err.message));
+          });
+          res.on("end", function () {
+            resolve(doc);
+          });
+        });
+      })
+      .catch(function (err: Error) {
+        return Bluebird.reject(new Exceptions.LdapSearchError(err.message));
+      });
+  }
+
+  modifyAsync(dn: string, changeRequest: any): Bluebird<void> {
+    return this.client.modifyAsync(dn, changeRequest);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorFactory.ts b/themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorFactory.ts
new file mode 100644
index 00000000..61fef07a
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorFactory.ts
@@ -0,0 +1,18 @@
+import { IConnector } from "./IConnector";
+import { Connector } from "./Connector";
+import { LdapConfiguration } from "../../../../configuration/schema/LdapConfiguration";
+import { Ldapjs } from "Dependencies";
+
+export class ConnectorFactory {
+  private configuration: LdapConfiguration;
+  private ldapjs: Ldapjs;
+
+  constructor(configuration: LdapConfiguration, ldapjs: Ldapjs) {
+    this.configuration = configuration;
+    this.ldapjs = ldapjs;
+  }
+
+  create(): IConnector {
+    return new Connector(this.configuration.url, this.ldapjs);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorFactoryStub.spec.ts b/themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorFactoryStub.spec.ts
new file mode 100644
index 00000000..d11fa638
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorFactoryStub.spec.ts
@@ -0,0 +1,17 @@
+import BluebirdPromise = require("bluebird");
+import Sinon = require("sinon");
+
+import { IConnectorFactory } from "./IConnectorFactory";
+import { IConnector } from "./IConnector";
+
+export class ConnectorFactoryStub implements IConnectorFactory {
+  createStub: Sinon.SinonStub;
+
+  constructor() {
+    this.createStub = Sinon.stub();
+  }
+
+  create(): IConnector {
+    return this.createStub();
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorStub.spec.ts b/themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorStub.spec.ts
new file mode 100644
index 00000000..0b78225b
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/connector/ConnectorStub.spec.ts
@@ -0,0 +1,34 @@
+import BluebirdPromise = require("bluebird");
+import Sinon = require("sinon");
+
+import { IConnector } from "./IConnector";
+
+export class ConnectorStub implements IConnector {
+  bindAsyncStub: Sinon.SinonStub;
+  unbindAsyncStub: Sinon.SinonStub;
+  searchAsyncStub: Sinon.SinonStub;
+  modifyAsyncStub: Sinon.SinonStub;
+
+  constructor() {
+    this.bindAsyncStub = Sinon.stub();
+    this.unbindAsyncStub = Sinon.stub();
+    this.searchAsyncStub = Sinon.stub();
+    this.modifyAsyncStub = Sinon.stub();
+  }
+
+  bindAsync(username: string, password: string): BluebirdPromise<void> {
+    return this.bindAsyncStub(username, password);
+  }
+
+  unbindAsync(): BluebirdPromise<void> {
+    return this.unbindAsyncStub();
+  }
+
+  searchAsync(base: string, query: any): BluebirdPromise<any[]> {
+    return this.searchAsyncStub(base, query);
+  }
+
+  modifyAsync(dn: string, changeRequest: any): BluebirdPromise<void> {
+    return this.modifyAsyncStub(dn, changeRequest);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/connector/IConnector.ts b/themes/squares/server/src/lib/authentication/backends/ldap/connector/IConnector.ts
new file mode 100644
index 00000000..1e63ab19
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/connector/IConnector.ts
@@ -0,0 +1,9 @@
+import Bluebird = require("bluebird");
+import EventEmitter = require("events");
+
+export interface IConnector {
+  bindAsync(username: string, password: string): Bluebird<void>;
+  unbindAsync(): Bluebird<void>;
+  searchAsync(base: string, query: any): Bluebird<any[]>;
+  modifyAsync(dn: string, changeRequest: any): Bluebird<void>;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/backends/ldap/connector/IConnectorFactory.ts b/themes/squares/server/src/lib/authentication/backends/ldap/connector/IConnectorFactory.ts
new file mode 100644
index 00000000..f9ed65ef
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/backends/ldap/connector/IConnectorFactory.ts
@@ -0,0 +1,5 @@
+import { IConnector } from "./IConnector";
+
+export interface IConnectorFactory {
+  create(): IConnector;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/totp/ITotpHandler.ts b/themes/squares/server/src/lib/authentication/totp/ITotpHandler.ts
new file mode 100644
index 00000000..d600d31e
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/totp/ITotpHandler.ts
@@ -0,0 +1,6 @@
+import { TOTPSecret } from "../../../../types/TOTPSecret";
+
+export interface ITotpHandler {
+  generate(label: string, issuer: string): TOTPSecret;
+  validate(token: string, secret: string): boolean;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/totp/TotpHandler.spec.ts b/themes/squares/server/src/lib/authentication/totp/TotpHandler.spec.ts
new file mode 100644
index 00000000..67cffa63
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/totp/TotpHandler.spec.ts
@@ -0,0 +1,39 @@
+
+import { TotpHandler } from "./TotpHandler";
+import Sinon = require("sinon");
+import Speakeasy = require("speakeasy");
+import Assert = require("assert");
+
+describe("authentication/totp/TotpHandler", function() {
+  let totpValidator: TotpHandler;
+  let validateStub: Sinon.SinonStub;
+
+  beforeEach(() => {
+    validateStub = Sinon.stub(Speakeasy.totp, "verify");
+    totpValidator = new TotpHandler(Speakeasy);
+  });
+
+  afterEach(function() {
+    validateStub.restore();
+  });
+
+  it("should validate the TOTP token", function() {
+    const totp_secret = "NBD2ZV64R9UV1O7K";
+    const token = "token";
+    validateStub.withArgs({
+      secret: totp_secret,
+      token: token,
+      encoding: "base32",
+      window: 1
+    }).returns(true);
+    Assert(totpValidator.validate(token, totp_secret));
+  });
+
+  it("should not validate a wrong TOTP token", function() {
+    const totp_secret = "NBD2ZV64R9UV1O7K";
+    const token = "wrong token";
+    validateStub.returns(false);
+    Assert(!totpValidator.validate(token, totp_secret));
+  });
+});
+
diff --git a/themes/squares/server/src/lib/authentication/totp/TotpHandler.ts b/themes/squares/server/src/lib/authentication/totp/TotpHandler.ts
new file mode 100644
index 00000000..dfab502a
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/totp/TotpHandler.ts
@@ -0,0 +1,36 @@
+import { ITotpHandler } from "./ITotpHandler";
+import { TOTPSecret } from "../../../../types/TOTPSecret";
+import Speakeasy = require("speakeasy");
+
+const TOTP_ENCODING = "base32";
+const WINDOW: number = 1;
+
+export class TotpHandler implements ITotpHandler {
+  private speakeasy: typeof Speakeasy;
+
+  constructor(speakeasy: typeof Speakeasy) {
+    this.speakeasy = speakeasy;
+  }
+
+  generate(label: string, issuer: string): TOTPSecret {
+    const secret = this.speakeasy.generateSecret({
+      otpauth_url: false
+    }) as TOTPSecret;
+
+    secret.otpauth_url = this.speakeasy.otpauthURL({
+      secret: secret.ascii,
+      label: label,
+      issuer: issuer
+    });
+    return secret;
+  }
+
+  validate(token: string, secret: string): boolean {
+    return this.speakeasy.totp.verify({
+      secret: secret,
+      encoding: TOTP_ENCODING,
+      token: token,
+      window: WINDOW
+    });
+  }
+}
diff --git a/themes/squares/server/src/lib/authentication/totp/TotpHandlerStub.spec.ts b/themes/squares/server/src/lib/authentication/totp/TotpHandlerStub.spec.ts
new file mode 100644
index 00000000..ea93330d
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/totp/TotpHandlerStub.spec.ts
@@ -0,0 +1,22 @@
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import { ITotpHandler } from "./ITotpHandler";
+import { TOTPSecret } from "../../../../types/TOTPSecret";
+
+export class TotpHandlerStub implements ITotpHandler {
+  generateStub: Sinon.SinonStub;
+  validateStub: Sinon.SinonStub;
+
+  constructor() {
+    this.generateStub = Sinon.stub();
+    this.validateStub = Sinon.stub();
+  }
+
+  generate(label: string, issuer: string): TOTPSecret {
+    return this.generateStub(label, issuer);
+  }
+
+  validate(token: string, secret: string): boolean {
+    return this.validateStub(token, secret);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/u2f/IU2fHandler.ts b/themes/squares/server/src/lib/authentication/u2f/IU2fHandler.ts
new file mode 100644
index 00000000..b9b7d6f2
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/u2f/IU2fHandler.ts
@@ -0,0 +1,9 @@
+import U2f = require("u2f");
+
+export interface IU2fHandler {
+  request(appId: string, keyHandle?: string): U2f.Request;
+  checkRegistration(registrationRequest: U2f.Request, registrationResponse: U2f.RegistrationData)
+    : U2f.RegistrationResult | U2f.Error;
+  checkSignature(signatureRequest: U2f.Request, signatureResponse: U2f.SignatureData, publicKey: string)
+    : U2f.SignatureResult | U2f.Error;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authentication/u2f/U2fHandler.ts b/themes/squares/server/src/lib/authentication/u2f/U2fHandler.ts
new file mode 100644
index 00000000..bf3891e5
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/u2f/U2fHandler.ts
@@ -0,0 +1,24 @@
+import { IU2fHandler } from "./IU2fHandler";
+import U2f = require("u2f");
+
+export class U2fHandler implements IU2fHandler {
+  private u2f: typeof U2f;
+
+  constructor(u2f: typeof U2f) {
+    this.u2f = u2f;
+  }
+
+  request(appId: string, keyHandle?: string): U2f.Request {
+    return this.u2f.request(appId, keyHandle);
+  }
+
+  checkRegistration(registrationRequest: U2f.Request, registrationResponse: U2f.RegistrationData)
+    : U2f.RegistrationResult | U2f.Error {
+    return this.u2f.checkRegistration(registrationRequest, registrationResponse);
+  }
+
+  checkSignature(signatureRequest: U2f.Request, signatureResponse: U2f.SignatureData, publicKey: string)
+    : U2f.SignatureResult | U2f.Error {
+    return this.u2f.checkSignature(signatureRequest, signatureResponse, publicKey);
+  }
+}
diff --git a/themes/squares/server/src/lib/authentication/u2f/U2fHandlerStub.spec.ts b/themes/squares/server/src/lib/authentication/u2f/U2fHandlerStub.spec.ts
new file mode 100644
index 00000000..135d7eb0
--- /dev/null
+++ b/themes/squares/server/src/lib/authentication/u2f/U2fHandlerStub.spec.ts
@@ -0,0 +1,31 @@
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import U2f = require("u2f");
+import { IU2fHandler } from "./IU2fHandler";
+
+
+export class U2fHandlerStub implements IU2fHandler {
+  requestStub: Sinon.SinonStub;
+  checkRegistrationStub: Sinon.SinonStub;
+  checkSignatureStub: Sinon.SinonStub;
+
+  constructor() {
+    this.requestStub = Sinon.stub();
+    this.checkRegistrationStub = Sinon.stub();
+    this.checkSignatureStub = Sinon.stub();
+  }
+
+  request(appId: string, keyHandle?: string): U2f.Request {
+    return this.requestStub(appId, keyHandle);
+  }
+
+  checkRegistration(registrationRequest: U2f.Request, registrationResponse: U2f.RegistrationData)
+    : U2f.RegistrationResult | U2f.Error {
+    return this.checkRegistrationStub(registrationRequest, registrationResponse);
+  }
+
+  checkSignature(signatureRequest: U2f.Request, signatureResponse: U2f.SignatureData, publicKey: string)
+    : U2f.SignatureResult | U2f.Error {
+    return this.checkSignatureStub(signatureRequest, signatureResponse, publicKey);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authorization/Authorizer.spec.ts b/themes/squares/server/src/lib/authorization/Authorizer.spec.ts
new file mode 100644
index 00000000..58681404
--- /dev/null
+++ b/themes/squares/server/src/lib/authorization/Authorizer.spec.ts
@@ -0,0 +1,372 @@
+
+import Assert = require("assert");
+import winston = require("winston");
+import { Authorizer } from "./Authorizer";
+import { ACLConfiguration, ACLRule } from "../configuration/schema/AclConfiguration";
+import { Level } from "./Level";
+
+describe("authorization/Authorizer", function () {
+  let authorizer: Authorizer;
+  let configuration: ACLConfiguration;
+
+  describe("configuration is null", function() {
+    it("should allow access to anything, anywhere for anybody", function() {
+      configuration = undefined;
+      authorizer = new Authorizer(configuration, winston);
+
+      Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1", "group2"]}), Level.BYPASS);
+      Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc"}, {user: "user1", groups: ["group1", "group2"]}), Level.BYPASS);
+      Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user2", groups: ["group1", "group2"]}), Level.BYPASS);
+      Assert.equal(authorizer.authorization({domain: "admin.example.com", resource: "/"}, {user: "user3", groups: ["group3"]}), Level.BYPASS);
+    });
+  });
+
+  describe("configuration is not null", function () {
+    beforeEach(function () {
+      configuration = {
+        default_policy: "deny",
+        rules: []
+      };
+      authorizer = new Authorizer(configuration, winston);
+    });
+
+    describe("check access control with default policy to deny", function () {
+      beforeEach(function () {
+        configuration.default_policy = "deny";
+      });
+
+      it("should deny access when no rule is provided", function () {
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+      });
+
+      it("should control access when multiple domain matcher is provided", function () {
+        configuration.rules = [{
+          domain: "*.mail.example.com",
+          policy: "two_factor",
+          subject: "user:user1",
+          resources: [".*"]
+        }];
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "mx1.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "mx1.server.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+      });
+
+      it("should allow access to all resources when resources is not provided", function () {
+        configuration.rules = [{
+          domain: "*.mail.example.com",
+          policy: "two_factor",
+          subject: "user:user1"
+        }];
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "mx1.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "mx1.server.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+      });
+
+      describe("check user rules", function () {
+        it("should allow access when user has a matching allowing rule", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: [".*"],
+            subject: "user:user1"
+          }];
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/another/resource"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "another.home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        });
+
+        it("should deny to other users", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: [".*"],
+            subject: "user:user1"
+          }];
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user2", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/another/resource"}, {user: "user2", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "another.home.example.com", resource: "/"}, {user: "user2", groups: ["group1"]}), Level.DENY);
+        });
+
+        it("should allow user access only to specific resources", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: ["/private/.*", "^/begin", "/end$"],
+            subject: "user:user1"
+          }];
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/class"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/middle/private/class"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/begin"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/not/begin"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc/end"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc/end/x"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        });
+
+        it("should allow access to multiple domains", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: [".*"],
+            subject: "user:user1"
+          }, {
+            domain: "home1.example.com",
+            policy: "one_factor",
+            resources: [".*"],
+            subject: "user:user1"
+          }, {
+            domain: "home2.example.com",
+            policy: "deny",
+            resources: [".*"],
+            subject: "user:user1"
+          }];
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home1.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.ONE_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home2.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "home3.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        });
+
+        it("should apply rules in order", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "one_factor",
+            resources: ["/my/private/resource"],
+            subject: "user:user1"
+          }, {
+            domain: "home.example.com",
+            policy: "deny",
+            resources: ["^/my/private/.*"],
+            subject: "user:user1"
+          }, {
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: ["^/my/.*"],
+            subject: "user:user1"
+          }];
+
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/poney"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/private/duck"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/private/resource"}, {user: "user1", groups: ["group1"]}), Level.ONE_FACTOR);
+        });
+      });
+
+      describe("check group rules", function () {
+        it("should allow access when user is in group having a matching allowing rule", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: ["^/$"],
+            subject: "group:group1"
+          }, {
+            domain: "home.example.com",
+            policy: "one_factor",
+            resources: ["^/test$"],
+            subject: "group:group2"
+          }, {
+            domain: "home.example.com",
+            policy: "deny",
+            resources: ["^/private$"],
+            subject: "group:group2"
+          }];
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"},
+            {user: "user1", groups: ["group1", "group2", "group3"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/test"},
+            {user: "user1", groups: ["group1", "group2", "group3"]}), Level.ONE_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"},
+            {user: "user1", groups: ["group1", "group2", "group3"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "another.home.example.com", resource: "/"},
+            {user: "user1", groups: ["group1", "group2", "group3"]}), Level.DENY);
+        });
+      });
+    });
+
+    describe("check any rules", function () {
+      it("should control access when any rules are defined", function () {
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "bypass",
+          resources: ["^/public$"]
+        }, {
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["^/private$"]
+        }];
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"},
+          {user: "user1", groups: ["group1", "group2", "group3"]}), Level.BYPASS);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"},
+          {user: "user1", groups: ["group1", "group2", "group3"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"},
+          {user: "user4", groups: ["group5"]}), Level.BYPASS);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"},
+          {user: "user4", groups: ["group5"]}), Level.DENY);
+      });
+    });
+
+    describe("check access control with default policy to allow", function () {
+      beforeEach(function () {
+        configuration.default_policy = "bypass";
+      });
+
+      it("should allow access to anything when no rule is provided", function () {
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/test"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
+      });
+
+      it("should deny access to one resource when defined", function () {
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["/test"],
+          subject: "user:user1"
+        }];
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/test"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
+      });
+    });
+
+    describe("check access control with complete use case", function () {
+      beforeEach(function () {
+        configuration.default_policy = "deny";
+      });
+
+      it("should control access of multiple user (real use case)", function () {
+        // Let say we have three users: admin, john, harry.
+        // admin is in groups ["admins"]
+        // john is in groups ["dev", "admin-private"]
+        // harry is in groups ["dev"]
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/public$", "^/$"]
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: [".*"],
+          subject: "group:admins"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/private/?.*"],
+          subject: "group:admin-private"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/private/john$"],
+          subject: "user:john"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/private/harry"],
+          subject: "user:harry"
+        }];
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "john", groups: ["dev", "admin-private"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev", "admin-private"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "john", groups: ["dev", "admin-private"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "harry", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "harry", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "harry", groups: ["dev"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "harry", groups: ["dev"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "harry", groups: ["dev"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "harry", groups: ["dev"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "harry", groups: ["dev"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "harry", groups: ["dev"]}), Level.TWO_FACTOR);
+      });
+
+      it("should allow when allowed at group level and denied at user level", function () {
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["^/dev/bob$"],
+          subject: "user:john"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/dev/?.*$"],
+          subject: "group:dev"
+        }];
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.DENY);
+      });
+
+      it("should allow access when allowed at 'any' level and denied at user level", function () {
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["^/dev/bob$"],
+          subject: "user:john"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/dev/?.*$"]
+        }];
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.DENY);
+      });
+
+      it("should allow access when allowed at 'any' level and denied at group level", function () {
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["^/dev/bob$"],
+          subject: "group:dev"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/dev/?.*$"]
+        }];
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.DENY);
+      });
+
+      it("should respect rules precedence", function () {
+        // the priority from least to most is 'default_policy', 'all', 'group', 'user'
+        // and the first rules in each category as a lower priority than the latest.
+        // You can think of it that way: they override themselves inside each category.
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/dev/?.*$"],
+          subject: "user:john"
+        }, {
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["^/dev/bob$"],
+          subject: "group:dev"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/dev/?.*$"]
+        }];
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
+      });
+    });
+  });
+});
diff --git a/themes/squares/server/src/lib/authorization/Authorizer.ts b/themes/squares/server/src/lib/authorization/Authorizer.ts
new file mode 100644
index 00000000..889b7ec2
--- /dev/null
+++ b/themes/squares/server/src/lib/authorization/Authorizer.ts
@@ -0,0 +1,85 @@
+
+import { ACLConfiguration, ACLPolicy, ACLRule } from "../configuration/schema/AclConfiguration";
+import { IAuthorizer } from "./IAuthorizer";
+import { Winston } from "../../../types/Dependencies";
+import { MultipleDomainMatcher } from "./MultipleDomainMatcher";
+import { Level } from "./Level";
+import { Object } from "./Object";
+import { Subject } from "./Subject";
+
+function MatchDomain(actualDomain: string) {
+  return function (rule: ACLRule): boolean {
+    return MultipleDomainMatcher.match(actualDomain, rule.domain);
+  };
+}
+
+function MatchResource(actualResource: string) {
+  return function (rule: ACLRule): boolean {
+    // If resources key is not provided, the rule applies to all resources.
+    if (!rule.resources) return true;
+
+    for (let i = 0; i < rule.resources.length; ++i) {
+      const regexp = new RegExp(rule.resources[i]);
+      if (regexp.test(actualResource)) return true;
+    }
+    return false;
+  };
+}
+
+function MatchSubject(subject: Subject) {
+  return (rule: ACLRule) => {
+    // If no subject, matches anybody
+    if (!rule.subject) return true;
+
+    if (rule.subject.startsWith("user:")) {
+      const ruleUser = rule.subject.split(":")[1];
+      if (subject.user == ruleUser) return true;
+    }
+
+    if (rule.subject.startsWith("group:")) {
+      const ruleGroup = rule.subject.split(":")[1];
+      if (subject.groups.indexOf(ruleGroup) > -1) return true;
+    }
+    return false;
+  };
+}
+
+export class Authorizer implements IAuthorizer {
+  private logger: Winston;
+  private readonly configuration: ACLConfiguration;
+
+  constructor(configuration: ACLConfiguration, logger_: Winston) {
+    this.logger = logger_;
+    this.configuration = configuration;
+  }
+
+  private getMatchingRules(object: Object, subject: Subject): ACLRule[] {
+    const rules = this.configuration.rules;
+    if (!rules) return [];
+    return rules
+      .filter(MatchDomain(object.domain))
+      .filter(MatchResource(object.resource))
+      .filter(MatchSubject(subject));
+  }
+
+  private ruleToLevel(policy: string): Level {
+    if (policy == "bypass") {
+      return Level.BYPASS;
+    } else if (policy == "one_factor") {
+      return Level.ONE_FACTOR;
+    } else if (policy == "two_factor") {
+      return Level.TWO_FACTOR;
+    }
+    return Level.DENY;
+  }
+
+  authorization(object: Object, subject: Subject): Level {
+    if (!this.configuration) return Level.BYPASS;
+
+    const rules = this.getMatchingRules(object, subject);
+
+    return (rules.length > 0)
+      ? this.ruleToLevel(rules[0].policy) // extract the policy of the first matching rule
+      : this.ruleToLevel(this.configuration.default_policy); // otherwise use the default policy
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authorization/AuthorizerStub.spec.ts b/themes/squares/server/src/lib/authorization/AuthorizerStub.spec.ts
new file mode 100644
index 00000000..9bd6f4a8
--- /dev/null
+++ b/themes/squares/server/src/lib/authorization/AuthorizerStub.spec.ts
@@ -0,0 +1,17 @@
+import Sinon = require("sinon");
+import { IAuthorizer } from "./IAuthorizer";
+import { Level } from "./Level";
+import { Object } from "./Object";
+import { Subject } from "./Subject";
+
+export class AuthorizerStub implements IAuthorizer {
+  authorizationMock: Sinon.SinonStub;
+
+  constructor() {
+    this.authorizationMock = Sinon.stub();
+  }
+
+  authorization(object: Object, subject: Subject): Level {
+    return this.authorizationMock(object, subject);
+  }
+}
diff --git a/themes/squares/server/src/lib/authorization/IAuthorizer.ts b/themes/squares/server/src/lib/authorization/IAuthorizer.ts
new file mode 100644
index 00000000..fe7ba367
--- /dev/null
+++ b/themes/squares/server/src/lib/authorization/IAuthorizer.ts
@@ -0,0 +1,7 @@
+import { Level } from "./Level";
+import { Subject } from "./Subject";
+import { Object } from "./Object";
+
+export interface IAuthorizer {
+  authorization(object: Object, subject: Subject): Level;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authorization/Level.ts b/themes/squares/server/src/lib/authorization/Level.ts
new file mode 100644
index 00000000..d1280261
--- /dev/null
+++ b/themes/squares/server/src/lib/authorization/Level.ts
@@ -0,0 +1,6 @@
+export enum Level {
+  BYPASS = 0,
+  ONE_FACTOR = 1,
+  TWO_FACTOR = 2,
+  DENY = 3
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authorization/MultipleDomainMatcher.ts b/themes/squares/server/src/lib/authorization/MultipleDomainMatcher.ts
new file mode 100644
index 00000000..64c647a4
--- /dev/null
+++ b/themes/squares/server/src/lib/authorization/MultipleDomainMatcher.ts
@@ -0,0 +1,12 @@
+
+export class MultipleDomainMatcher {
+  static match(domain: string, pattern: string): boolean {
+    if (pattern.startsWith("*") &&
+      domain.endsWith(pattern.substr(1))) {
+      return true;
+    }
+    else if (domain == pattern) {
+      return true;
+    }
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authorization/Object.ts b/themes/squares/server/src/lib/authorization/Object.ts
new file mode 100644
index 00000000..5411b0d2
--- /dev/null
+++ b/themes/squares/server/src/lib/authorization/Object.ts
@@ -0,0 +1,5 @@
+
+export interface Object {
+  domain: string;
+  resource: string;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/authorization/Subject.ts b/themes/squares/server/src/lib/authorization/Subject.ts
new file mode 100644
index 00000000..310d6b4c
--- /dev/null
+++ b/themes/squares/server/src/lib/authorization/Subject.ts
@@ -0,0 +1,5 @@
+
+export interface Subject {
+  user: string;
+  groups: string[];
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/ConfigurationParser.spec.ts b/themes/squares/server/src/lib/configuration/ConfigurationParser.spec.ts
new file mode 100644
index 00000000..60c0f618
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/ConfigurationParser.spec.ts
@@ -0,0 +1,171 @@
+import * as Assert from "assert";
+import { Configuration } from "./schema/Configuration";
+import { ACLConfiguration } from "./schema/AclConfiguration";
+import { ConfigurationParser } from "./ConfigurationParser";
+
+describe("configuration/ConfigurationParser", function () {
+  function buildYamlConfig(): Configuration {
+    const yaml_config: Configuration = {
+      port: 8080,
+      authentication_backend: {
+        ldap: {
+          url: "http://ldap",
+          base_dn: "dc=example,dc=com",
+          additional_users_dn: "ou=users",
+          additional_groups_dn: "ou=groups",
+          user: "user",
+          password: "pass"
+        },
+      },
+      session: {
+        domain: "example.com",
+        secret: "secret",
+        expiration: 40000
+      },
+      storage: {
+        local: {
+          path: "/mydirectory"
+        }
+      },
+      regulation: {
+        max_retries: 3,
+        find_time: 5 * 60,
+        ban_time: 5 * 60
+      },
+      logs_level: "debug",
+      notifier: {
+        email: {
+          username: "user",
+          password: "password",
+          sender: "admin@example.com",
+          service: "gmail"
+        }
+      }
+    };
+    return yaml_config;
+  }
+
+  describe("port", function () {
+    it("should read the port from the yaml file", function () {
+      const yaml_config = buildYamlConfig();
+      yaml_config.port = 7070;
+      const config = ConfigurationParser.parse(yaml_config);
+      Assert.equal(config.port, 7070);
+    });
+
+    it("should default the port to 8080 if not provided", function () {
+      const yaml_config = buildYamlConfig();
+      delete yaml_config.port;
+      const config = ConfigurationParser.parse(yaml_config);
+      Assert.equal(config.port, 8080);
+    });
+  });
+
+  describe("test session configuration", function() {
+    it("should get the session attributes", function () {
+      const yaml_config = buildYamlConfig();
+      yaml_config.session = {
+        domain: "example.com",
+        secret: "secret",
+        expiration: 3600,
+        inactivity: 4000
+      };
+      const config = ConfigurationParser.parse(yaml_config);
+      Assert.equal(config.session.domain, "example.com");
+      Assert.equal(config.session.secret, "secret");
+      Assert.equal(config.session.expiration, 3600);
+      Assert.equal(config.session.inactivity, 4000);
+    });
+
+    it("should be ok not specifying inactivity", function () {
+      const yaml_config = buildYamlConfig();
+      yaml_config.session = {
+        domain: "example.com",
+        secret: "secret",
+        expiration: 3600
+      };
+      const config = ConfigurationParser.parse(yaml_config);
+      Assert.equal(config.session.domain, "example.com");
+      Assert.equal(config.session.secret, "secret");
+      Assert.equal(config.session.expiration, 3600);
+      Assert.equal(config.session.inactivity, undefined);
+    });
+  });
+
+  it("should get the log level", function () {
+    const yaml_config = buildYamlConfig();
+    yaml_config.logs_level = "debug";
+    const config = ConfigurationParser.parse(yaml_config);
+    Assert.equal(config.logs_level, "debug");
+  });
+
+  it("should get the notifier config", function () {
+    const userConfig = buildYamlConfig();
+    userConfig.notifier = {
+      email: {
+        username: "user",
+        password: "pass",
+        sender: "admin@example.com",
+        service: "gmail"
+      }
+    };
+    const config = ConfigurationParser.parse(userConfig);
+    Assert.deepEqual(config.notifier, {
+      email: {
+        username: "user",
+        password: "pass",
+        sender: "admin@example.com",
+        service: "gmail"
+      }
+    });
+  });
+
+  describe("access_control", function() {
+    it("should adapt access_control when it is already ok", function () {
+      const userConfig = buildYamlConfig();
+      userConfig.access_control = {
+        default_policy: "deny",
+        rules: [{
+          domain: "www.example.com",
+          policy: "two_factor",
+          subject: "user:user"
+        }, {
+          domain: "public.example.com",
+          policy: "two_factor"
+        }]
+      };
+      const config = ConfigurationParser.parse(userConfig);
+      Assert.deepEqual(config.access_control, {
+        default_policy: "deny",
+        rules: [{
+          domain: "www.example.com",
+          policy: "two_factor",
+          subject: "user:user"
+        }, {
+          domain: "public.example.com",
+          policy: "two_factor"
+        }]
+      } as ACLConfiguration);
+    });
+
+
+    it("should adapt access_control when it is empty", function () {
+      const userConfig = buildYamlConfig();
+      userConfig.access_control = {} as any;
+      const config = ConfigurationParser.parse(userConfig);
+      Assert.deepEqual(config.access_control, {
+        default_policy: "bypass",
+        rules: []
+      });
+    });
+  });
+
+  describe("default_redirection_url", function() {
+    it("should parse default_redirection_url", function() {
+      const userConfig = buildYamlConfig();
+      userConfig.default_redirection_url = "dummy_url";
+      const config = ConfigurationParser.parse(userConfig);
+      Assert.deepEqual(config.default_redirection_url, "dummy_url");
+    });
+  });
+});
diff --git a/themes/squares/server/src/lib/configuration/ConfigurationParser.ts b/themes/squares/server/src/lib/configuration/ConfigurationParser.ts
new file mode 100644
index 00000000..d92d163c
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/ConfigurationParser.ts
@@ -0,0 +1,39 @@
+
+import * as ObjectPath from "object-path";
+import { Configuration, complete } from "./schema/Configuration";
+import Ajv = require("ajv");
+import Path = require("path");
+import Util = require("util");
+
+export class ConfigurationParser {
+  private static parseTypes(configuration: Configuration): string[] {
+    const schema = require(Path.resolve(__dirname, "./Configuration.schema.json"));
+    const ajv = new Ajv({
+      allErrors: true,
+      missingRefs: "fail"
+    });
+    ajv.addMetaSchema(require("ajv/lib/refs/json-schema-draft-06.json"));
+    const valid = ajv.validate(schema, configuration);
+    if (!valid)
+      return ajv.errors.map(
+        (e: Ajv.ErrorObject) => { return ajv.errorsText([e]); });
+    return [];
+  }
+
+  static parse(configuration: Configuration): Configuration {
+    const validationErrors = this.parseTypes(configuration);
+    if (validationErrors.length > 0) {
+      validationErrors.forEach((e: string) => { console.log(e); });
+      throw new Error("Malformed configuration (schema). Please double-check your configuration file.");
+    }
+
+    const [newConfiguration, completionErrors] = complete(configuration);
+
+    if (completionErrors.length > 0) {
+      completionErrors.forEach((e: string) => { console.log(e); });
+      throw new Error("Malformed configuration (validator). Please double-check your configuration file.");
+    }
+    return newConfiguration;
+  }
+}
+
diff --git a/themes/squares/server/src/lib/configuration/SessionConfigurationBuilder.spec.ts b/themes/squares/server/src/lib/configuration/SessionConfigurationBuilder.spec.ts
new file mode 100644
index 00000000..d4a3093e
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/SessionConfigurationBuilder.spec.ts
@@ -0,0 +1,149 @@
+import { SessionConfigurationBuilder } from "./SessionConfigurationBuilder";
+import { Configuration } from "./schema/Configuration";
+import { GlobalDependencies } from "../../../types/Dependencies";
+
+import ExpressSession = require("express-session");
+import ConnectRedis = require("connect-redis");
+import Sinon = require("sinon");
+import Assert = require("assert");
+
+describe("configuration/SessionConfigurationBuilder", function () {
+  const configuration: Configuration = {
+    access_control: {
+      default_policy: "deny",
+      rules: []
+    },
+    totp: {
+      issuer: "authelia.com"
+    },
+    authentication_backend: {
+      ldap: {
+        url: "ldap://ldap",
+        user: "user",
+        base_dn: "dc=example,dc=com",
+        password: "password",
+        additional_groups_dn: "ou=groups",
+        additional_users_dn: "ou=users",
+        group_name_attribute: "",
+        groups_filter: "",
+        mail_attribute: "",
+        users_filter: ""
+      },
+    },
+    logs_level: "debug",
+    notifier: {
+      filesystem: {
+        filename: "/test"
+      }
+    },
+    port: 8080,
+    session: {
+      name: "authelia_session",
+      domain: "example.com",
+      expiration: 3600,
+      secret: "secret"
+    },
+    regulation: {
+      max_retries: 3,
+      ban_time: 5 * 60,
+      find_time: 5 * 60
+    },
+    storage: {
+      local: {
+        in_memory: true
+      }
+    }
+  };
+
+  const deps: GlobalDependencies = {
+    ConnectRedis: Sinon.spy() as any,
+    ldapjs: Sinon.spy() as any,
+    nedb: Sinon.spy() as any,
+    session: Sinon.spy() as any,
+    speakeasy: Sinon.spy() as any,
+    u2f: Sinon.spy() as any,
+    winston: Sinon.spy() as any,
+    Redis: Sinon.spy() as any
+  };
+
+  it("should return session options without redis options", function () {
+    const options = SessionConfigurationBuilder.build(configuration, deps);
+    const expectedOptions = {
+      name: "authelia_session",
+      secret: "secret",
+      resave: false,
+      saveUninitialized: true,
+      cookie: {
+        secure: true,
+        httpOnly: true,
+        maxAge: 3600,
+        domain: "example.com"
+      }
+    };
+
+    Assert.deepEqual(expectedOptions, options);
+  });
+
+  it("should return session options with redis options", function () {
+    configuration.session["redis"] = {
+      host: "redis.example.com",
+      port: 6379
+    };
+    const RedisStoreMock = Sinon.spy();
+    const redisClient = Sinon.mock().returns({ on: Sinon.spy() });
+
+    deps.ConnectRedis = Sinon.stub().returns(RedisStoreMock) as any;
+    deps.Redis = {
+      createClient: Sinon.mock().returns(redisClient)
+    } as any;
+
+    const options = SessionConfigurationBuilder.build(configuration, deps);
+
+    const expectedOptions: ExpressSession.SessionOptions = {
+      secret: "secret",
+      resave: false,
+      saveUninitialized: true,
+      name: "authelia_session",
+      cookie: {
+        secure: true,
+        httpOnly: true,
+        maxAge: 3600,
+        domain: "example.com"
+      },
+      store: Sinon.match.object as any
+    };
+
+    Assert((deps.ConnectRedis as Sinon.SinonStub).calledWith(deps.session));
+    Assert.equal(options.secret, expectedOptions.secret);
+    Assert.equal(options.resave, expectedOptions.resave);
+    Assert.equal(options.saveUninitialized, expectedOptions.saveUninitialized);
+    Assert.deepEqual(options.cookie, expectedOptions.cookie);
+    Assert(options.store != undefined);
+  });
+
+  it("should return session options with redis password", function () {
+    configuration.session["redis"] = {
+      host: "redis.example.com",
+      port: 6379,
+      password: "authelia_pass"
+    };
+    const RedisStoreMock = Sinon.spy();
+    const redisClient = Sinon.mock().returns({ on: Sinon.spy() });
+    const createClientStub = Sinon.stub();
+
+    deps.ConnectRedis = Sinon.stub().returns(RedisStoreMock) as any;
+    deps.Redis = {
+      createClient: createClientStub
+    } as any;
+
+    createClientStub.returns(redisClient);
+
+    const options = SessionConfigurationBuilder.build(configuration, deps);
+
+    Assert(createClientStub.calledWith({
+      host: "redis.example.com",
+      port: 6379,
+      password: "authelia_pass"
+    }));
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/SessionConfigurationBuilder.ts b/themes/squares/server/src/lib/configuration/SessionConfigurationBuilder.ts
new file mode 100644
index 00000000..6ce643d9
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/SessionConfigurationBuilder.ts
@@ -0,0 +1,52 @@
+import ExpressSession = require("express-session");
+import Redis = require("redis");
+
+import { Configuration } from "./schema/Configuration";
+import { GlobalDependencies } from "../../../types/Dependencies";
+import { RedisStoreOptions } from "connect-redis";
+
+export class SessionConfigurationBuilder {
+
+  static build(configuration: Configuration, deps: GlobalDependencies): ExpressSession.SessionOptions {
+    const sessionOptions: ExpressSession.SessionOptions = {
+      name: configuration.session.name,
+      secret: configuration.session.secret,
+      resave: false,
+      saveUninitialized: true,
+      cookie: {
+        secure: true,
+        httpOnly: true,
+        maxAge: configuration.session.expiration,
+        domain: configuration.session.domain
+      },
+    };
+
+    if (configuration.session.redis) {
+      let redisOptions;
+      const options: Redis.ClientOpts = {
+        host: configuration.session.redis.host,
+        port: configuration.session.redis.port
+      };
+
+      if (configuration.session.redis.password) {
+        options["password"] = configuration.session.redis.password;
+      }
+      const client = deps.Redis.createClient(options);
+
+      client.on("error", function (err: Error) {
+        console.error("Redis error:", err);
+      });
+
+      redisOptions = {
+        client: client,
+        logErrors: true
+      };
+
+      if (redisOptions) {
+        const RedisStore = deps.ConnectRedis(deps.session);
+        sessionOptions.store = new RedisStore(redisOptions);
+      }
+    }
+    return sessionOptions;
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/AclConfiguration.spec.ts b/themes/squares/server/src/lib/configuration/schema/AclConfiguration.spec.ts
new file mode 100644
index 00000000..d1e2a03a
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/AclConfiguration.spec.ts
@@ -0,0 +1,34 @@
+import { ACLConfiguration, complete } from "./AclConfiguration";
+import Assert = require("assert");
+
+describe("configuration/schema/AclConfiguration", function() {
+  it("should complete ACLConfiguration", function() {
+    const configuration: ACLConfiguration = {};
+    const [newConfiguration, errors] = complete(configuration);
+
+    Assert.deepEqual(newConfiguration.default_policy, "bypass");
+    Assert.deepEqual(newConfiguration.rules, []);
+  });
+
+  it("should return errors when subject is not good", function() {
+    const configuration: ACLConfiguration = {
+      default_policy: "deny",
+      rules: [{
+        domain: "dev.example.com",
+        subject: "user:abc",
+        policy: "bypass"
+      }, {
+        domain: "dev.example.com",
+        subject: "user:def",
+        policy: "bypass"
+      }, {
+        domain: "dev.example.com",
+        subject: "badkey:abc",
+        policy: "bypass"
+      }]
+    };
+    const [newConfiguration, errors] = complete(configuration);
+
+    Assert.deepEqual(errors, ["Rule 2 has wrong subject. It should be starting with user: or group:."]);
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/AclConfiguration.ts b/themes/squares/server/src/lib/configuration/schema/AclConfiguration.ts
new file mode 100644
index 00000000..40401dd6
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/AclConfiguration.ts
@@ -0,0 +1,41 @@
+
+export type ACLPolicy = "deny" | "bypass" | "one_factor" | "two_factor";
+
+export type ACLRule = {
+  domain: string;
+  resources?: string[];
+  subject?: string;
+  policy: ACLPolicy;
+};
+
+export interface ACLConfiguration {
+  default_policy?: ACLPolicy;
+  rules?: ACLRule[];
+}
+
+export function complete(configuration: ACLConfiguration): [ACLConfiguration, string[]] {
+  const newConfiguration: ACLConfiguration = (configuration)
+    ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.default_policy) {
+    newConfiguration.default_policy = "bypass";
+  }
+
+  if (!newConfiguration.rules) {
+    newConfiguration.rules = [];
+  }
+
+  if (newConfiguration.rules.length > 0) {
+    const errors: string[] = [];
+    newConfiguration.rules.forEach((r, idx) => {
+      if (r.subject && !r.subject.match(/^(user|group):[a-zA-Z0-9]+$/)) {
+        errors.push(`Rule ${idx} has wrong subject. It should be starting with user: or group:.`);
+      }
+    });
+    if (errors.length > 0) {
+      return [newConfiguration, errors];
+    }
+  }
+
+  return [newConfiguration, []];
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.spec.ts b/themes/squares/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.spec.ts
new file mode 100644
index 00000000..3ca86381
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.spec.ts
@@ -0,0 +1,11 @@
+import { AuthenticationBackendConfiguration, complete } from "./AuthenticationBackendConfiguration";
+import Assert = require("assert");
+
+describe("configuration/schema/AuthenticationBackendConfiguration", function() {
+  it("should ensure there is at least one key", function() {
+    const configuration: AuthenticationBackendConfiguration = {} as any;
+    const [newConfiguration, error] = complete(configuration);
+
+    Assert.equal(error, "Authentication backend must have one of the following keys:`ldap` or `file`");
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.ts b/themes/squares/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.ts
new file mode 100644
index 00000000..7f77f894
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.ts
@@ -0,0 +1,25 @@
+import { LdapConfiguration } from "./LdapConfiguration";
+import { FileUsersDatabaseConfiguration } from "./FileUsersDatabaseConfiguration";
+
+export interface AuthenticationBackendConfiguration {
+  ldap?: LdapConfiguration;
+  file?: FileUsersDatabaseConfiguration;
+}
+
+export function complete(
+  configuration: AuthenticationBackendConfiguration)
+  : [AuthenticationBackendConfiguration, string] {
+
+  const newConfiguration: AuthenticationBackendConfiguration = (configuration)
+    ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (Object.keys(newConfiguration).length != 1) {
+    return [
+      newConfiguration,
+      "Authentication backend must have one of the following keys:" +
+      "`ldap` or `file`"
+    ];
+  }
+
+  return [newConfiguration, undefined];
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/Configuration.ts b/themes/squares/server/src/lib/configuration/schema/Configuration.ts
new file mode 100644
index 00000000..8d16a5fb
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/Configuration.ts
@@ -0,0 +1,68 @@
+import { ACLConfiguration, complete as AclConfigurationComplete } from "./AclConfiguration";
+import { AuthenticationBackendConfiguration, complete as AuthenticationBackendComplete } from "./AuthenticationBackendConfiguration";
+import { NotifierConfiguration, complete as NotifierConfigurationComplete } from "./NotifierConfiguration";
+import { RegulationConfiguration, complete as RegulationConfigurationComplete } from "./RegulationConfiguration";
+import { SessionConfiguration, complete as SessionConfigurationComplete } from "./SessionConfiguration";
+import { StorageConfiguration, complete as StorageConfigurationComplete } from "./StorageConfiguration";
+import { TotpConfiguration, complete as TotpConfigurationComplete } from "./TotpConfiguration";
+
+export interface Configuration {
+  access_control?: ACLConfiguration;
+  authentication_backend: AuthenticationBackendConfiguration;
+  default_redirection_url?: string;
+  logs_level?: string;
+  notifier?: NotifierConfiguration;
+  port?: number;
+  regulation?: RegulationConfiguration;
+  session?: SessionConfiguration;
+  storage?: StorageConfiguration;
+  totp?: TotpConfiguration;
+}
+
+export function complete(
+  configuration: Configuration):
+  [Configuration, string[]] {
+
+  const newConfiguration: Configuration = JSON.parse(
+    JSON.stringify(configuration));
+  const errors: string[] = [];
+
+  const [acls, aclsErrors] = AclConfigurationComplete(
+    newConfiguration.access_control);
+
+  newConfiguration.access_control = acls;
+  if (aclsErrors.length > 0) {
+    errors.concat(aclsErrors);
+  }
+
+  const [backend, error] =
+    AuthenticationBackendComplete(
+      newConfiguration.authentication_backend);
+
+  if (error) errors.push(error);
+  newConfiguration.authentication_backend = backend;
+
+  if (!newConfiguration.logs_level) {
+    newConfiguration.logs_level = "info";
+  }
+
+  const [notifier, notifierError] = NotifierConfigurationComplete(
+    newConfiguration.notifier);
+  newConfiguration.notifier = notifier;
+  if (notifierError) errors.push(notifierError);
+
+  if (!newConfiguration.port) {
+    newConfiguration.port = 8080;
+  }
+
+  newConfiguration.regulation = RegulationConfigurationComplete(
+    newConfiguration.regulation);
+  newConfiguration.session = SessionConfigurationComplete(
+    newConfiguration.session);
+  newConfiguration.storage = StorageConfigurationComplete(
+    newConfiguration.storage);
+  newConfiguration.totp = TotpConfigurationComplete(
+    newConfiguration.totp);
+
+  return [newConfiguration, errors];
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/FileUsersDatabaseConfiguration.ts b/themes/squares/server/src/lib/configuration/schema/FileUsersDatabaseConfiguration.ts
new file mode 100644
index 00000000..d19002ba
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/FileUsersDatabaseConfiguration.ts
@@ -0,0 +1,4 @@
+
+export interface FileUsersDatabaseConfiguration {
+  path: string;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/LdapConfiguration.spec.ts b/themes/squares/server/src/lib/configuration/schema/LdapConfiguration.spec.ts
new file mode 100644
index 00000000..cc73d108
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/LdapConfiguration.spec.ts
@@ -0,0 +1,25 @@
+import Assert = require("assert");
+import { LdapConfiguration, complete } from "./LdapConfiguration";
+
+describe("configuration/schema/AuthenticationMethodsConfiguration", function() {
+  it("should ensure at least one key is provided", function() {
+    const configuration: LdapConfiguration = {
+      url: "ldap.example.com",
+      base_dn: "dc=example,dc=com",
+      user: "admin",
+      password: "password"
+    };
+    const newConfiguration = complete(configuration);
+
+    Assert.deepEqual(newConfiguration, {
+      url: "ldap.example.com",
+      base_dn: "dc=example,dc=com",
+      user: "admin",
+      password: "password",
+      users_filter: "cn={0}",
+      group_name_attribute: "cn",
+      groups_filter: "member={dn}",
+      mail_attribute: "mail"
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/LdapConfiguration.ts b/themes/squares/server/src/lib/configuration/schema/LdapConfiguration.ts
new file mode 100644
index 00000000..5dacb939
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/LdapConfiguration.ts
@@ -0,0 +1,40 @@
+import Util = require("util");
+
+export interface LdapConfiguration {
+  url: string;
+  base_dn: string;
+
+  additional_users_dn?: string;
+  users_filter?: string;
+
+  additional_groups_dn?: string;
+  groups_filter?: string;
+
+  group_name_attribute?: string;
+  mail_attribute?: string;
+
+  user: string; // admin username
+  password: string; // admin password
+}
+
+export function complete(configuration: LdapConfiguration): LdapConfiguration {
+  const newConfiguration: LdapConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.users_filter) {
+    newConfiguration.users_filter = "cn={0}";
+  }
+
+  if (!newConfiguration.groups_filter) {
+    newConfiguration.groups_filter = "member={dn}";
+  }
+
+  if (!newConfiguration.group_name_attribute) {
+    newConfiguration.group_name_attribute = "cn";
+  }
+
+  if (!newConfiguration.mail_attribute) {
+    newConfiguration.mail_attribute = "mail";
+  }
+
+  return newConfiguration;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/NotifierConfiguration.spec.ts b/themes/squares/server/src/lib/configuration/schema/NotifierConfiguration.spec.ts
new file mode 100644
index 00000000..6c576e8e
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/NotifierConfiguration.spec.ts
@@ -0,0 +1,40 @@
+import Assert = require("assert");
+import { NotifierConfiguration, complete } from "./NotifierConfiguration";
+
+describe("configuration/schema/NotifierConfiguration", function() {
+  it("should use a default notifier when none is provided", function() {
+    const configuration: NotifierConfiguration = {};
+    const [newConfiguration, error] = complete(configuration);
+
+    Assert.deepEqual(newConfiguration.filesystem, {filename: "/tmp/authelia/notification.txt"});
+  });
+
+  it("should ensure correct key is provided", function() {
+    const configuration = {
+      abc: "badvalue"
+    };
+    const [newConfiguration, error] = complete(configuration as any);
+
+    Assert.equal(error, "Notifier must have one of the following keys: 'filesystem', 'email' or 'smtp'");
+  });
+
+  it("should ensure there is no more than one key", function() {
+    const configuration: NotifierConfiguration = {
+      smtp: {
+        host: "smtp.example.com",
+        port: 25,
+        secure: false,
+        sender: "test@example.com"
+      },
+      email: {
+        username: "test",
+        password: "test",
+        sender: "test@example.com",
+        service: "gmail"
+      }
+    };
+    const [newConfiguration, error] = complete(configuration);
+
+    Assert.equal(error, "Notifier must have one of the following keys: 'filesystem', 'email' or 'smtp'");
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/NotifierConfiguration.ts b/themes/squares/server/src/lib/configuration/schema/NotifierConfiguration.ts
new file mode 100644
index 00000000..7bcce15c
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/NotifierConfiguration.ts
@@ -0,0 +1,45 @@
+
+export interface EmailNotifierConfiguration {
+  username: string;
+  password: string;
+  sender: string;
+  service: string;
+}
+
+export interface SmtpNotifierConfiguration {
+  username?: string;
+  password?: string;
+  host: string;
+  port: number;
+  secure: boolean;
+  sender: string;
+}
+
+export interface FileSystemNotifierConfiguration {
+  filename: string;
+}
+
+export interface NotifierConfiguration {
+  email?: EmailNotifierConfiguration;
+  smtp?: SmtpNotifierConfiguration;
+  filesystem?: FileSystemNotifierConfiguration;
+}
+
+export function complete(configuration: NotifierConfiguration): [NotifierConfiguration, string] {
+  const newConfiguration: NotifierConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (Object.keys(newConfiguration).length == 0)
+    newConfiguration.filesystem = { filename: "/tmp/authelia/notification.txt" };
+
+  const ERROR = "Notifier must have one of the following keys: 'filesystem', 'email' or 'smtp'";
+
+  if (Object.keys(newConfiguration).length != 1)
+    return [newConfiguration, ERROR];
+
+  const key = Object.keys(newConfiguration)[0];
+
+  if (key != "filesystem" && key != "smtp" && key != "email")
+    return [newConfiguration, ERROR];
+
+  return [newConfiguration, undefined];
+}
diff --git a/themes/squares/server/src/lib/configuration/schema/RegulationConfiguration.spec.ts b/themes/squares/server/src/lib/configuration/schema/RegulationConfiguration.spec.ts
new file mode 100644
index 00000000..dce2caf4
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/RegulationConfiguration.spec.ts
@@ -0,0 +1,13 @@
+import Assert = require("assert");
+import { RegulationConfiguration, complete } from "./RegulationConfiguration";
+
+describe("configuration/schema/RegulationConfiguration", function() {
+  it("should return default regulation configuration", function() {
+    const configuration: RegulationConfiguration = {};
+    const newConfiguration = complete(configuration);
+
+    Assert.equal(newConfiguration.ban_time, 300);
+    Assert.equal(newConfiguration.find_time, 120);
+    Assert.equal(newConfiguration.max_retries, 3);
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/RegulationConfiguration.ts b/themes/squares/server/src/lib/configuration/schema/RegulationConfiguration.ts
new file mode 100644
index 00000000..117463f4
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/RegulationConfiguration.ts
@@ -0,0 +1,23 @@
+export interface RegulationConfiguration {
+  max_retries?: number;
+  find_time?: number;
+  ban_time?: number;
+}
+
+export function complete(configuration: RegulationConfiguration): RegulationConfiguration {
+  const newConfiguration: RegulationConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.max_retries) {
+    newConfiguration.max_retries = 3;
+  }
+
+  if (!newConfiguration.find_time) {
+    newConfiguration.find_time = 120; // seconds
+  }
+
+  if (!newConfiguration.ban_time) {
+    newConfiguration.ban_time = 300; // seconds
+  }
+
+  return newConfiguration;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/SessionConfiguration.spec.ts b/themes/squares/server/src/lib/configuration/schema/SessionConfiguration.spec.ts
new file mode 100644
index 00000000..e5401083
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/SessionConfiguration.spec.ts
@@ -0,0 +1,16 @@
+import Assert = require("assert");
+import { SessionConfiguration, complete } from "./SessionConfiguration";
+
+describe("configuration/schema/SessionConfiguration", function() {
+  it("should return default regulation configuration", function() {
+    const configuration: SessionConfiguration = {
+      domain: "example.com",
+      secret: "unsecure_secret"
+    };
+    const newConfiguration = complete(configuration);
+
+    Assert.equal(newConfiguration.name, 'authelia_session');
+    Assert.equal(newConfiguration.expiration, 3600000);
+    Assert.equal(newConfiguration.inactivity, undefined);
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/SessionConfiguration.ts b/themes/squares/server/src/lib/configuration/schema/SessionConfiguration.ts
new file mode 100644
index 00000000..2c88bb21
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/SessionConfiguration.ts
@@ -0,0 +1,32 @@
+export interface SessionRedisOptions {
+  host: string;
+  port: number;
+  password?: string;
+}
+
+export interface SessionConfiguration {
+  name?: string;
+  domain: string;
+  secret: string;
+  expiration?: number;
+  inactivity?: number;
+  redis?: SessionRedisOptions;
+}
+
+export function complete(configuration: SessionConfiguration): SessionConfiguration {
+  const newConfiguration: SessionConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.name) {
+    newConfiguration.name = "authelia_session";
+  }
+
+  if (!newConfiguration.expiration) {
+    newConfiguration.expiration = 3600000; // 1 hour
+  }
+
+  if (!newConfiguration.inactivity) {
+    newConfiguration.inactivity = undefined; // disabled
+  }
+
+  return newConfiguration;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/StorageConfiguration.spec.ts b/themes/squares/server/src/lib/configuration/schema/StorageConfiguration.spec.ts
new file mode 100644
index 00000000..9d02a11b
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/StorageConfiguration.spec.ts
@@ -0,0 +1,15 @@
+import Assert = require("assert");
+import { StorageConfiguration, complete } from "./StorageConfiguration";
+
+describe("configuration/schema/StorageConfiguration", function() {
+  it("should return default regulation configuration", function() {
+    const configuration: StorageConfiguration = {};
+    const newConfiguration = complete(configuration);
+
+    Assert.deepEqual(newConfiguration, {
+      local: {
+        in_memory: true
+      }
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/StorageConfiguration.ts b/themes/squares/server/src/lib/configuration/schema/StorageConfiguration.ts
new file mode 100644
index 00000000..47e356ef
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/StorageConfiguration.ts
@@ -0,0 +1,30 @@
+export interface MongoStorageConfiguration {
+  url: string;
+  database: string;
+  auth?: {
+    username: string;
+    password: string;
+  };
+}
+
+export interface LocalStorageConfiguration {
+  path?: string;
+  in_memory?: boolean;
+}
+
+export interface StorageConfiguration {
+  local?: LocalStorageConfiguration;
+  mongo?: MongoStorageConfiguration;
+}
+
+export function complete(configuration: StorageConfiguration): StorageConfiguration {
+  const newConfiguration: StorageConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.local && !newConfiguration.mongo) {
+    newConfiguration.local = {
+      in_memory: true
+    };
+  }
+
+  return newConfiguration;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/TotpConfiguration.ts b/themes/squares/server/src/lib/configuration/schema/TotpConfiguration.ts
new file mode 100644
index 00000000..68313563
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/TotpConfiguration.ts
@@ -0,0 +1,13 @@
+export interface TotpConfiguration {
+  issuer: string;
+}
+
+export function complete(configuration: TotpConfiguration): TotpConfiguration {
+  const newConfiguration: TotpConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.issuer) {
+    newConfiguration.issuer = "authelia.com";
+  }
+
+  return newConfiguration;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/configuration/schema/UserDatabaseConfiguration.ts b/themes/squares/server/src/lib/configuration/schema/UserDatabaseConfiguration.ts
new file mode 100644
index 00000000..8008b483
--- /dev/null
+++ b/themes/squares/server/src/lib/configuration/schema/UserDatabaseConfiguration.ts
@@ -0,0 +1,9 @@
+
+export interface UserInfo {
+  username: string;
+  password_hash: string;
+  email: string;
+  groups?: string[];
+}
+
+export type UserDatabaseConfiguration = UserInfo[];
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/connectors/mongo/IMongoClient.d.ts b/themes/squares/server/src/lib/connectors/mongo/IMongoClient.d.ts
new file mode 100644
index 00000000..36cb4b8b
--- /dev/null
+++ b/themes/squares/server/src/lib/connectors/mongo/IMongoClient.d.ts
@@ -0,0 +1,6 @@
+import MongoDB = require("mongodb");
+import Bluebird = require("bluebird");
+
+export interface IMongoClient {
+    collection(name: string): Bluebird<MongoDB.Collection>
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/connectors/mongo/MongoClient.spec.ts b/themes/squares/server/src/lib/connectors/mongo/MongoClient.spec.ts
new file mode 100644
index 00000000..ca0c6859
--- /dev/null
+++ b/themes/squares/server/src/lib/connectors/mongo/MongoClient.spec.ts
@@ -0,0 +1,119 @@
+import Assert = require("assert");
+import Bluebird = require("bluebird");
+import MongoDB = require("mongodb");
+import Sinon = require("sinon");
+
+import { MongoClient } from "./MongoClient";
+import { GlobalLoggerStub } from "../../logging/GlobalLoggerStub.spec";
+import { MongoStorageConfiguration } from "../../configuration/schema/StorageConfiguration";
+
+describe("connectors/mongo/MongoClient", function () {
+  let MongoClientStub: any;
+  let mongoClientStub: any;
+  let mongoDatabaseStub: any;
+  let logger: GlobalLoggerStub = new GlobalLoggerStub();
+
+  const configuration: MongoStorageConfiguration = {
+    url: "mongo://url",
+    database: "databasename"
+  };
+
+  describe("connection", () => {
+    before(() => {
+      mongoClientStub = {
+        db: Sinon.stub()
+      };
+      mongoDatabaseStub = {
+        on: Sinon.stub(),
+        collection: Sinon.stub()
+      }
+      MongoClientStub = Sinon.stub(
+        MongoDB.MongoClient, "connect");
+      MongoClientStub.yields(
+        undefined, mongoClientStub);
+      mongoClientStub.db.returns(
+        mongoDatabaseStub);
+    });
+
+    after(() => {
+      MongoClientStub.restore();
+    });
+
+    it("should use credentials from configuration", () => {
+      configuration.auth = {
+        username: "authelia",
+        password: "authelia_pass"
+      };
+
+      const client = new MongoClient(configuration, logger);
+      return client.collection("test")
+        .then(() => {
+          Assert(MongoClientStub.calledWith("mongo://url", {
+            auth: {
+              user: "authelia",
+              password: "authelia_pass"
+            }
+          }))
+        });
+    });
+  });
+
+  describe("collection", () => {
+    before(function() {
+      mongoClientStub = {
+        db: Sinon.stub()
+      };
+      mongoDatabaseStub = {
+        on: Sinon.stub(),
+        collection: Sinon.stub()
+      }
+    });
+
+    describe("Connection to mongo is ok", function() {
+      before(function () {
+        MongoClientStub = Sinon.stub(
+          MongoDB.MongoClient, "connect");
+        MongoClientStub.yields(
+          undefined, mongoClientStub);
+        mongoClientStub.db.returns(
+          mongoDatabaseStub);
+      });
+  
+      after(function () {
+        MongoClientStub.restore();
+      });
+  
+      it("should create a collection", function () {
+        const COLLECTION_NAME = "mycollection";
+        const client = new MongoClient(configuration, logger);
+  
+        mongoDatabaseStub.collection.returns("COL");
+        return client.collection(COLLECTION_NAME)
+          .then((collection) => mongoDatabaseStub.collection.calledWith(COLLECTION_NAME));
+      });
+    });
+
+    describe("Connection to mongo is broken", function() {
+      before(function () {
+        MongoClientStub = Sinon.stub(
+          MongoDB.MongoClient, "connect");
+        MongoClientStub.yields(
+          new Error("Failed connection"), undefined);
+      });
+  
+      after(function () {
+        MongoClientStub.restore();
+      });
+
+      it("should fail creating the collection", function() {
+        const COLLECTION_NAME = "mycollection";
+        const client = new MongoClient(configuration, logger);
+  
+        mongoDatabaseStub.collection.returns("COL");
+        return client.collection(COLLECTION_NAME)
+          .then((collection) => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => Bluebird.resolve());
+      });
+    })
+  });
+});
diff --git a/themes/squares/server/src/lib/connectors/mongo/MongoClient.ts b/themes/squares/server/src/lib/connectors/mongo/MongoClient.ts
new file mode 100644
index 00000000..d15731e9
--- /dev/null
+++ b/themes/squares/server/src/lib/connectors/mongo/MongoClient.ts
@@ -0,0 +1,76 @@
+
+import MongoDB = require("mongodb");
+import { IMongoClient } from "./IMongoClient";
+import Bluebird = require("bluebird");
+import { AUTHENTICATION_FAILED } from "../../../../../shared/UserMessages";
+import { IGlobalLogger } from "../../logging/IGlobalLogger";
+import { MongoStorageConfiguration } from "../../configuration/schema/StorageConfiguration";
+
+export class MongoClient implements IMongoClient {
+  private configuration: MongoStorageConfiguration;
+
+  private database: MongoDB.Db;
+  private client: MongoDB.MongoClient;
+  private logger: IGlobalLogger;
+
+  constructor(
+    configuration: MongoStorageConfiguration,
+    logger: IGlobalLogger) {
+
+    this.configuration = configuration;
+    this.logger = logger;
+  }
+
+  connect(): Bluebird<void> {
+    const that = this;
+    const options: MongoDB.MongoClientOptions = {};
+    if (that.configuration.auth) {
+      options["auth"] = {
+        user: that.configuration.auth.username,
+        password: that.configuration.auth.password
+      };
+    }
+
+    return new Bluebird((resolve, reject) => {
+        MongoDB.MongoClient.connect(
+          this.configuration.url,
+          options,
+          function(err, client) {
+          if (err) {
+            reject(err);
+            return;
+          }
+          resolve(client);
+        });
+      })
+      .then(function (client: MongoDB.MongoClient) {
+        that.database = client.db(that.configuration.database);
+        that.database.on("close", () => {
+          that.logger.info("[MongoClient] Lost connection.");
+        });
+        that.database.on("reconnect", () => {
+          that.logger.info("[MongoClient] Reconnected.");
+        });
+        that.client = client;
+      });
+  }
+
+  close(): Bluebird<void> {
+    if (this.client) {
+      this.client.close();
+      this.database = undefined;
+      this.client = undefined;
+    }
+    return Bluebird.resolve();
+  }
+
+  collection(name: string): Bluebird<MongoDB.Collection> {
+    if (!this.client) {
+      const that = this;
+      return this.connect()
+        .then(() => Bluebird.resolve(that.database.collection(name)));
+    }
+
+    return Bluebird.resolve(this.database.collection(name));
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/connectors/mongo/MongoClientStub.spec.ts b/themes/squares/server/src/lib/connectors/mongo/MongoClientStub.spec.ts
new file mode 100644
index 00000000..1cfd48e3
--- /dev/null
+++ b/themes/squares/server/src/lib/connectors/mongo/MongoClientStub.spec.ts
@@ -0,0 +1,16 @@
+import Sinon = require("sinon");
+import MongoDB = require("mongodb");
+import Bluebird = require("bluebird");
+import { IMongoClient } from "../../../../src/lib/connectors/mongo/IMongoClient";
+
+export class MongoClientStub implements IMongoClient {
+  public collectionStub: Sinon.SinonStub;
+
+  constructor() {
+    this.collectionStub = Sinon.stub();
+  }
+
+  collection(name: string): Bluebird<MongoDB.Collection> {
+    return this.collectionStub(name);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/logging/GlobalLogger.ts b/themes/squares/server/src/lib/logging/GlobalLogger.ts
new file mode 100644
index 00000000..4da7acf4
--- /dev/null
+++ b/themes/squares/server/src/lib/logging/GlobalLogger.ts
@@ -0,0 +1,34 @@
+import { IGlobalLogger } from "./IGlobalLogger";
+import Util = require("util");
+import Express = require("express");
+import Winston = require("winston");
+
+declare module "express" {
+  interface Request {
+    id: string;
+  }
+}
+
+export class GlobalLogger implements IGlobalLogger {
+  private winston: typeof Winston;
+  constructor(winston: typeof Winston) {
+    this.winston = winston;
+  }
+
+  private buildMessage(message: string, ...args: any[]): string {
+    return Util.format("date='%s' message='%s'", new Date(),
+      Util.format(message, ...args));
+  }
+
+  info(message: string, ...args: any[]): void {
+    this.winston.info(this.buildMessage(message, ...args));
+  }
+
+  debug(message: string, ...args: any[]): void {
+    this.winston.debug(this.buildMessage(message, ...args));
+  }
+
+  error(message: string, ...args: any[]): void {
+    this.winston.debug(this.buildMessage(message, ...args));
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/logging/GlobalLoggerStub.spec.ts b/themes/squares/server/src/lib/logging/GlobalLoggerStub.spec.ts
new file mode 100644
index 00000000..d4bb1371
--- /dev/null
+++ b/themes/squares/server/src/lib/logging/GlobalLoggerStub.spec.ts
@@ -0,0 +1,38 @@
+import Sinon = require("sinon");
+import { GlobalLogger } from "./GlobalLogger";
+import Winston = require("winston");
+import Express = require("express");
+import { IGlobalLogger } from "./IGlobalLogger";
+
+export class GlobalLoggerStub implements IGlobalLogger {
+  infoStub: Sinon.SinonStub;
+  debugStub: Sinon.SinonStub;
+  errorStub: Sinon.SinonStub;
+  private globalLogger: IGlobalLogger;
+
+  constructor(enableLogging?: boolean) {
+    this.infoStub = Sinon.stub();
+    this.debugStub = Sinon.stub();
+    this.errorStub = Sinon.stub();
+    if (enableLogging)
+      this.globalLogger = new GlobalLogger(Winston);
+  }
+
+  info(message: string, ...args: any[]): void {
+    if (this.globalLogger)
+      this.globalLogger.info(message, ...args);
+    this.infoStub(message, ...args);
+  }
+
+  debug(message: string, ...args: any[]): void {
+    if (this.globalLogger)
+      this.globalLogger.info(message, ...args);
+    this.debugStub(message, ...args);
+  }
+
+  error(message: string, ...args: any[]): void {
+    if (this.globalLogger)
+      this.globalLogger.info(message, ...args);
+    this.errorStub(message, ...args);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/logging/IGlobalLogger.ts b/themes/squares/server/src/lib/logging/IGlobalLogger.ts
new file mode 100644
index 00000000..548515ec
--- /dev/null
+++ b/themes/squares/server/src/lib/logging/IGlobalLogger.ts
@@ -0,0 +1,5 @@
+export interface IGlobalLogger {
+  info(message: string, ...args: any[]): void;
+  debug(message: string, ...args: any[]): void;
+  error(message: string, ...args: any[]): void;
+}
diff --git a/themes/squares/server/src/lib/logging/IRequestLogger.ts b/themes/squares/server/src/lib/logging/IRequestLogger.ts
new file mode 100644
index 00000000..126a601f
--- /dev/null
+++ b/themes/squares/server/src/lib/logging/IRequestLogger.ts
@@ -0,0 +1,7 @@
+import Express = require("express");
+
+export interface IRequestLogger {
+  info(req: Express.Request, message: string, ...args: any[]): void;
+  debug(req: Express.Request, message: string, ...args: any[]): void;
+  error(req: Express.Request, message: string, ...args: any[]): void;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/logging/RequestLogger.ts b/themes/squares/server/src/lib/logging/RequestLogger.ts
new file mode 100644
index 00000000..c45c6601
--- /dev/null
+++ b/themes/squares/server/src/lib/logging/RequestLogger.ts
@@ -0,0 +1,45 @@
+import { IRequestLogger } from "./IRequestLogger";
+import Util = require("util");
+import Express = require("express");
+import Winston = require("winston");
+
+declare module "express" {
+  interface Request {
+    id: string;
+  }
+}
+
+export class RequestLogger implements IRequestLogger {
+  private winston: typeof Winston;
+
+  constructor(winston: typeof Winston) {
+    this.winston = winston;
+  }
+
+  private formatHeader(req: Express.Request) {
+    const clientIP = req.ip; // The IP of the original client going through the proxy chain.
+    return Util.format("date='%s' method='%s', path='%s' requestId='%s' sessionId='%s' ip='%s'",
+      new Date(), req.method, req.path, req.id, req.sessionID, clientIP);
+  }
+
+  private formatBody(message: string) {
+    return Util.format("message='%s'", message);
+  }
+
+  private formatMessage(req: Express.Request, message: string) {
+    return Util.format("%s %s", this.formatHeader(req),
+      this.formatBody(message));
+  }
+
+  info(req: Express.Request, message: string, ...args: any[]): void {
+    this.winston.info(this.formatMessage(req, message), ...args);
+  }
+
+  debug(req: Express.Request, message: string, ...args: any[]): void {
+    this.winston.debug(this.formatMessage(req, message), ...args);
+  }
+
+  error(req: Express.Request, message: string, ...args: any[]): void {
+    this.winston.error(this.formatMessage(req, message), ...args);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/logging/RequestLoggerStub.spec.ts b/themes/squares/server/src/lib/logging/RequestLoggerStub.spec.ts
new file mode 100644
index 00000000..b0e37521
--- /dev/null
+++ b/themes/squares/server/src/lib/logging/RequestLoggerStub.spec.ts
@@ -0,0 +1,38 @@
+import { IRequestLogger } from "./IRequestLogger";
+import Sinon = require("sinon");
+import { RequestLogger } from "./RequestLogger";
+import Winston = require("winston");
+import Express = require("express");
+
+export class RequestLoggerStub implements IRequestLogger {
+  infoStub: Sinon.SinonStub;
+  debugStub: Sinon.SinonStub;
+  errorStub: Sinon.SinonStub;
+  private requestLogger: RequestLogger;
+
+  constructor(enableLogging?: boolean) {
+    this.infoStub = Sinon.stub();
+    this.debugStub = Sinon.stub();
+    this.errorStub = Sinon.stub();
+    if (enableLogging)
+      this.requestLogger = new RequestLogger(Winston);
+  }
+
+  info(req: Express.Request, message: string, ...args: any[]): void {
+    if (this.requestLogger)
+      this.requestLogger.info(req, message, ...args);
+    this.infoStub(req, message, ...args);
+  }
+
+  debug(req: Express.Request, message: string, ...args: any[]): void {
+    if (this.requestLogger)
+      this.requestLogger.info(req, message, ...args);
+    this.debugStub(req, message, ...args);
+  }
+
+  error(req: Express.Request, message: string, ...args: any[]): void {
+    if (this.requestLogger)
+      this.requestLogger.info(req, message, ...args);
+    this.errorStub(req, message, ...args);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/AbstractEmailNotifier.ts b/themes/squares/server/src/lib/notifiers/AbstractEmailNotifier.ts
new file mode 100644
index 00000000..198e4e5d
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/AbstractEmailNotifier.ts
@@ -0,0 +1,23 @@
+
+import { INotifier } from "../notifiers/INotifier";
+import { Identity } from "../../../types/Identity";
+
+import Fs = require("fs");
+import Path = require("path");
+import Ejs = require("ejs");
+import BluebirdPromise = require("bluebird");
+
+const email_template = Fs.readFileSync(Path.join(__dirname, "../../resources/email-template.ejs"), "UTF-8");
+
+export abstract class AbstractEmailNotifier implements INotifier {
+  notify(to: string, subject: string, link: string): BluebirdPromise<void> {
+    const d = {
+      url: link,
+      button_title: "Continue",
+      title: subject
+    };
+    return this.sendEmail(to, subject, Ejs.render(email_template, d));
+  }
+
+  abstract sendEmail(to: string, subject: string, content: string): BluebirdPromise<void>;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/EmailNotifier.spec.ts b/themes/squares/server/src/lib/notifiers/EmailNotifier.spec.ts
new file mode 100644
index 00000000..8211bbc0
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/EmailNotifier.spec.ts
@@ -0,0 +1,54 @@
+import * as sinon from "sinon";
+import * as Assert from "assert";
+import BluebirdPromise = require("bluebird");
+
+import { MailSenderStub } from "./MailSenderStub.spec";
+import EmailNotifier = require("./EmailNotifier");
+
+
+describe("notifiers/EmailNotifier", function () {
+  it("should send an email to given user", function () {
+    const mailSender = new MailSenderStub();
+    const options = {
+      username: "user_gmail",
+      password: "pass_gmail",
+      sender: "admin@example.com",
+      service: "gmail"
+    };
+
+    mailSender.sendStub.returns(BluebirdPromise.resolve());
+    const sender = new EmailNotifier.EmailNotifier(options, mailSender);
+    const subject = "subject";
+    const url = "http://test.com";
+
+    return sender.notify("user@example.com", subject, url)
+      .then(function () {
+        Assert.equal(mailSender.sendStub.getCall(0).args[0].to, "user@example.com");
+        Assert.equal(mailSender.sendStub.getCall(0).args[0].subject, "subject");
+        return BluebirdPromise.resolve();
+      });
+  });
+
+  it("should fail while sending an email", function () {
+    const mailSender = new MailSenderStub();
+    const options = {
+      username: "user_gmail",
+      password: "pass_gmail",
+      sender: "admin@example.com",
+      service: "gmail"
+    };
+
+    mailSender.sendStub.returns(BluebirdPromise.reject(new Error("Failed to send mail")));
+    const sender = new EmailNotifier.EmailNotifier(options, mailSender);
+    const subject = "subject";
+    const url = "http://test.com";
+
+    return sender.notify("user@example.com", subject, url)
+      .then(function () {
+        return BluebirdPromise.reject(new Error());
+      }, function() {
+        Assert.equal(mailSender.sendStub.getCall(0).args[0].from, "admin@example.com");
+        return BluebirdPromise.resolve();
+      });
+  });
+});
diff --git a/themes/squares/server/src/lib/notifiers/EmailNotifier.ts b/themes/squares/server/src/lib/notifiers/EmailNotifier.ts
new file mode 100644
index 00000000..4df7c861
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/EmailNotifier.ts
@@ -0,0 +1,27 @@
+
+import * as BluebirdPromise from "bluebird";
+
+import { AbstractEmailNotifier } from "../notifiers/AbstractEmailNotifier";
+import { EmailNotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+import { IMailSender } from "./IMailSender";
+
+export class EmailNotifier extends AbstractEmailNotifier {
+  private mailSender: IMailSender;
+  private sender: string;
+
+  constructor(options: EmailNotifierConfiguration, mailSender: IMailSender) {
+    super();
+    this.mailSender = mailSender;
+    this.sender = options.sender;
+  }
+
+  sendEmail(to: string, subject: string, content: string) {
+    const mailOptions = {
+      from: this.sender,
+      to: to,
+      subject: subject,
+      html: content
+    };
+    return this.mailSender.send(mailOptions);
+  }
+}
diff --git a/themes/squares/server/src/lib/notifiers/FileSystemNotifier.ts b/themes/squares/server/src/lib/notifiers/FileSystemNotifier.ts
new file mode 100644
index 00000000..23f6242c
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/FileSystemNotifier.ts
@@ -0,0 +1,22 @@
+import * as BluebirdPromise from "bluebird";
+import * as util from "util";
+import * as Fs from "fs";
+import { INotifier } from "./INotifier";
+import { Identity } from "../../../types/Identity";
+
+import { FileSystemNotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+
+export class FileSystemNotifier implements INotifier {
+  private filename: string;
+
+  constructor(options: FileSystemNotifierConfiguration) {
+    this.filename = options.filename;
+  }
+
+  notify(to: string, subject: string, link: string): BluebirdPromise<void> {
+    const content = util.format("Date: %s\nEmail: %s\nSubject: %s\nLink: %s",
+      new Date().toString(), to, subject, link);
+    const writeFilePromised: any = BluebirdPromise.promisify(Fs.writeFile);
+    return writeFilePromised(this.filename, content);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/IMailSender.ts b/themes/squares/server/src/lib/notifiers/IMailSender.ts
new file mode 100644
index 00000000..34ac464a
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/IMailSender.ts
@@ -0,0 +1,6 @@
+import BluebirdPromise = require("bluebird");
+import Nodemailer = require("nodemailer");
+
+export interface IMailSender {
+  send(mailOptions: Nodemailer.SendMailOptions): BluebirdPromise<void>;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/IMailSenderBuilder.ts b/themes/squares/server/src/lib/notifiers/IMailSenderBuilder.ts
new file mode 100644
index 00000000..36d4dcdf
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/IMailSenderBuilder.ts
@@ -0,0 +1,7 @@
+import { IMailSender } from "./IMailSender";
+import { SmtpNotifierConfiguration, EmailNotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+
+export interface IMailSenderBuilder {
+  buildEmail(options: EmailNotifierConfiguration): IMailSender;
+  buildSmtp(options: SmtpNotifierConfiguration): IMailSender;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/INotifier.ts b/themes/squares/server/src/lib/notifiers/INotifier.ts
new file mode 100644
index 00000000..b9a6b138
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/INotifier.ts
@@ -0,0 +1,5 @@
+import * as BluebirdPromise from "bluebird";
+
+export interface INotifier {
+  notify(to: string, subject: string, link: string): BluebirdPromise<void>;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/MailSender.ts b/themes/squares/server/src/lib/notifiers/MailSender.ts
new file mode 100644
index 00000000..536a88e6
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/MailSender.ts
@@ -0,0 +1,42 @@
+import { IMailSender } from "./IMailSender";
+import Nodemailer = require("nodemailer");
+import NodemailerDirectTransport = require("nodemailer-direct-transport");
+import NodemailerSmtpTransport = require("nodemailer-smtp-transport");
+import BluebirdPromise = require("bluebird");
+
+export class MailSender implements IMailSender {
+  private transporter: Nodemailer.Transporter;
+
+  constructor(options: NodemailerDirectTransport.DirectOptions |
+    NodemailerSmtpTransport.SmtpOptions, nodemailer: typeof Nodemailer) {
+    this.transporter = nodemailer.createTransport(options);
+  }
+
+  verify(): BluebirdPromise<void> {
+    const that = this;
+    return new BluebirdPromise(function (resolve, reject) {
+      that.transporter.verify(function (error: Error, success: any) {
+        if (error) {
+          reject(new Error("Unable to connect to SMTP server. \
+  Please check the service is running and your credentials are correct."));
+          return;
+        }
+        resolve();
+      });
+    });
+  }
+
+  send(mailOptions: Nodemailer.SendMailOptions): BluebirdPromise<void> {
+    const that = this;
+    return new BluebirdPromise(function (resolve, reject) {
+      that.transporter.sendMail(mailOptions, (error: Error,
+        data: Nodemailer.SentMessageInfo) => {
+        if (error) {
+          reject(new Error("Error while sending email: " + error.message));
+          return;
+        }
+        resolve();
+      });
+    });
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/MailSenderBuilder.spec.ts b/themes/squares/server/src/lib/notifiers/MailSenderBuilder.spec.ts
new file mode 100644
index 00000000..41e0db42
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/MailSenderBuilder.spec.ts
@@ -0,0 +1,67 @@
+
+import { MailSenderBuilder } from ".//MailSenderBuilder";
+import Nodemailer = require("nodemailer");
+import Sinon = require("sinon");
+import Assert = require("assert");
+
+describe("notifiers/MailSenderBuilder", function() {
+  let createTransportStub: Sinon.SinonStub;
+  beforeEach(function() {
+    createTransportStub = Sinon.stub(Nodemailer, "createTransport");
+  });
+
+  afterEach(function() {
+    createTransportStub.restore();
+  });
+
+  it("should create a email mail sender", function() {
+    const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
+    mailSenderBuilder.buildEmail({
+      username: "user_gmail",
+      password: "pass_gmail",
+      sender: "admin@example.com",
+      service: "gmail"
+    });
+    Assert.equal(createTransportStub.getCall(0).args[0].auth.user, "user_gmail");
+    Assert.equal(createTransportStub.getCall(0).args[0].auth.pass, "pass_gmail");
+    Assert.equal(createTransportStub.getCall(0).args[0].service, "gmail");
+  });
+
+  describe("build smtp mail sender", function() {
+    it("should create a smtp mail sender with authenticated user", function() {
+      const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
+      mailSenderBuilder.buildSmtp({
+        host: "mail.example.com",
+        password: "password",
+        port: 25,
+        secure: true,
+        username: "user",
+        sender: "admin@example.com"
+      });
+      Assert.deepStrictEqual(createTransportStub.getCall(0).args[0], {
+        host: "mail.example.com",
+        auth: {
+          pass: "password",
+          user: "user"
+        },
+        port: 25,
+        secure: true,
+      });
+    });
+
+    it("should create a smtp mail sender with anonymous user", function() {
+      const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
+      mailSenderBuilder.buildSmtp({
+        host: "mail.example.com",
+        port: 25,
+        secure: true,
+        sender: "admin@example.com"
+      });
+      Assert.deepStrictEqual(createTransportStub.getCall(0).args[0], {
+        host: "mail.example.com",
+        port: 25,
+        secure: true,
+      });
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/MailSenderBuilder.ts b/themes/squares/server/src/lib/notifiers/MailSenderBuilder.ts
new file mode 100644
index 00000000..1d06be52
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/MailSenderBuilder.ts
@@ -0,0 +1,42 @@
+import { IMailSender } from "./IMailSender";
+import { IMailSenderBuilder } from "./IMailSenderBuilder";
+import { MailSender } from "./MailSender";
+import Nodemailer = require("nodemailer");
+import NodemailerSmtpTransport = require("nodemailer-smtp-transport");
+import { SmtpNotifierConfiguration, EmailNotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+
+export class MailSenderBuilder implements IMailSenderBuilder {
+  private nodemailer: typeof Nodemailer;
+
+  constructor(nodemailer: typeof Nodemailer) {
+    this.nodemailer = nodemailer;
+  }
+
+  buildEmail(options: EmailNotifierConfiguration): IMailSender {
+    const emailOptions = {
+      service: options.service,
+      auth: {
+        user: options.username,
+        pass: options.password
+      }
+    };
+    return new MailSender(emailOptions, this.nodemailer);
+  }
+
+  buildSmtp(options: SmtpNotifierConfiguration): IMailSender {
+    const smtpOptions: NodemailerSmtpTransport.SmtpOptions = {
+      host: options.host,
+      port: options.port,
+      secure: options.secure, // upgrade later with STARTTLS
+    };
+
+    if (options.username && options.password) {
+      smtpOptions.auth = {
+        user: options.username,
+        pass: options.password
+      };
+    }
+
+    return new MailSender(smtpOptions, this.nodemailer);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/MailSenderBuilderStub.spec.ts b/themes/squares/server/src/lib/notifiers/MailSenderBuilderStub.spec.ts
new file mode 100644
index 00000000..5b76f6e5
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/MailSenderBuilderStub.spec.ts
@@ -0,0 +1,25 @@
+import { IMailSenderBuilder } from "../../../src/lib/notifiers/IMailSenderBuilder";
+import BluebirdPromise = require("bluebird");
+import Nodemailer = require("nodemailer");
+import Sinon = require("sinon");
+import { IMailSender } from "../../../src/lib/notifiers/IMailSender";
+import { SmtpNotifierConfiguration, EmailNotifierConfiguration } from "../../../src/lib/configuration/schema/NotifierConfiguration";
+
+export class MailSenderBuilderStub implements IMailSenderBuilder {
+  buildEmailStub: Sinon.SinonStub;
+  buildSmtpStub: Sinon.SinonStub;
+
+  constructor() {
+    this.buildEmailStub = Sinon.stub();
+    this.buildSmtpStub = Sinon.stub();
+  }
+
+  buildEmail(options: EmailNotifierConfiguration): IMailSender {
+    return this.buildEmailStub(options);
+  }
+
+  buildSmtp(options: SmtpNotifierConfiguration): IMailSender {
+    return this.buildSmtpStub(options);
+  }
+
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/MailSenderStub.spec.ts b/themes/squares/server/src/lib/notifiers/MailSenderStub.spec.ts
new file mode 100644
index 00000000..d57c458f
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/MailSenderStub.spec.ts
@@ -0,0 +1,16 @@
+import { IMailSender } from "../../../src/lib/notifiers/IMailSender";
+import BluebirdPromise = require("bluebird");
+import Nodemailer = require("nodemailer");
+import Sinon = require("sinon");
+
+export class MailSenderStub implements IMailSender {
+  sendStub: Sinon.SinonStub;
+
+  constructor() {
+    this.sendStub = Sinon.stub();
+  }
+
+  send(mailOptions: Nodemailer.SendMailOptions): BluebirdPromise<void> {
+    return this.sendStub(mailOptions);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/NotifierFactory.spec.ts b/themes/squares/server/src/lib/notifiers/NotifierFactory.spec.ts
new file mode 100644
index 00000000..f15e7667
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/NotifierFactory.spec.ts
@@ -0,0 +1,42 @@
+
+import * as sinon from "sinon";
+import * as BluebirdPromise from "bluebird";
+import * as assert from "assert";
+
+import { NotifierFactory } from "./NotifierFactory";
+import { EmailNotifier } from "./EmailNotifier";
+import { SmtpNotifier } from "./SmtpNotifier";
+import { MailSenderBuilderStub } from "./MailSenderBuilderStub.spec";
+
+
+describe("notifiers/NotifierFactory", function () {
+  let mailSenderBuilderStub: MailSenderBuilderStub;
+  it("should build a Email Notifier", function () {
+    const options = {
+      email: {
+        username: "abc",
+        password: "password",
+        sender: "admin@example.com",
+        service: "gmail"
+      }
+    };
+    mailSenderBuilderStub = new MailSenderBuilderStub();
+    assert(NotifierFactory.build(options, mailSenderBuilderStub) instanceof EmailNotifier);
+  });
+
+  it("should build a SMTP Notifier", function () {
+    const options = {
+      smtp: {
+        username: "user",
+        password: "pass",
+        secure: true,
+        host: "localhost",
+        port: 25,
+        sender: "admin@example.com"
+      }
+    };
+
+    mailSenderBuilderStub = new MailSenderBuilderStub();
+    assert(NotifierFactory.build(options, mailSenderBuilderStub) instanceof SmtpNotifier);
+  });
+});
diff --git a/themes/squares/server/src/lib/notifiers/NotifierFactory.ts b/themes/squares/server/src/lib/notifiers/NotifierFactory.ts
new file mode 100644
index 00000000..a89155fe
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/NotifierFactory.ts
@@ -0,0 +1,33 @@
+
+import { NotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+import Nodemailer = require("nodemailer");
+import { INotifier } from "./INotifier";
+
+import { FileSystemNotifier } from "./FileSystemNotifier";
+import { EmailNotifier } from "./EmailNotifier";
+import { SmtpNotifier } from "./SmtpNotifier";
+import { IMailSender } from "./IMailSender";
+import { IMailSenderBuilder } from "./IMailSenderBuilder";
+
+export class NotifierFactory {
+  static build(options: NotifierConfiguration, mailSenderBuilder: IMailSenderBuilder): INotifier {
+    if ("email" in options) {
+      const mailSender = mailSenderBuilder.buildEmail(options.email);
+      return new EmailNotifier(options.email, mailSender);
+    }
+    else if ("smtp" in options) {
+      const mailSender = mailSenderBuilder.buildSmtp(options.smtp);
+      return new SmtpNotifier(options.smtp, mailSender);
+    }
+    else if ("filesystem" in options) {
+      return new FileSystemNotifier(options.filesystem);
+    }
+    else {
+      throw new Error("No available notifier option detected.");
+    }
+  }
+}
+
+
+
+
diff --git a/themes/squares/server/src/lib/notifiers/NotifierStub.spec.ts b/themes/squares/server/src/lib/notifiers/NotifierStub.spec.ts
new file mode 100644
index 00000000..f99231b5
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/NotifierStub.spec.ts
@@ -0,0 +1,16 @@
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+
+import { INotifier } from "./INotifier";
+
+export class NotifierStub implements INotifier {
+    notifyStub: Sinon.SinonStub;
+
+    constructor() {
+        this.notifyStub = Sinon.stub();
+    }
+
+    notify(to: string, subject: string, link: string): BluebirdPromise<void> {
+        return this.notifyStub(to, subject, link);
+    }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/notifiers/SmtpNotifier.ts b/themes/squares/server/src/lib/notifiers/SmtpNotifier.ts
new file mode 100644
index 00000000..f93a6d4a
--- /dev/null
+++ b/themes/squares/server/src/lib/notifiers/SmtpNotifier.ts
@@ -0,0 +1,30 @@
+
+
+import * as BluebirdPromise from "bluebird";
+
+import { IMailSender } from "./IMailSender";
+import { AbstractEmailNotifier } from "../notifiers/AbstractEmailNotifier";
+import { SmtpNotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+
+export class SmtpNotifier extends AbstractEmailNotifier {
+  private mailSender: IMailSender;
+  private sender: string;
+
+  constructor(options: SmtpNotifierConfiguration,
+    mailSender: IMailSender) {
+    super();
+    this.mailSender = mailSender;
+    this.sender = options.sender;
+  }
+
+  sendEmail(to: string, subject: string, content: string) {
+    const mailOptions = {
+      from: this.sender,
+      to: to,
+      subject: subject,
+      html: content
+    };
+    const that = this;
+    return this.mailSender.send(mailOptions);
+  }
+}
diff --git a/themes/squares/server/src/lib/regulation/IRegulator.ts b/themes/squares/server/src/lib/regulation/IRegulator.ts
new file mode 100644
index 00000000..c49425b2
--- /dev/null
+++ b/themes/squares/server/src/lib/regulation/IRegulator.ts
@@ -0,0 +1,6 @@
+import BluebirdPromise = require("bluebird");
+
+export interface IRegulator {
+  mark(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void>;
+  regulate(userId: string): BluebirdPromise<void>;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/regulation/Regulator.spec.ts b/themes/squares/server/src/lib/regulation/Regulator.spec.ts
new file mode 100644
index 00000000..f9c6e608
--- /dev/null
+++ b/themes/squares/server/src/lib/regulation/Regulator.spec.ts
@@ -0,0 +1,186 @@
+
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+
+import { Regulator } from "./Regulator";
+import MockDate = require("mockdate");
+import exceptions = require("../Exceptions");
+import { UserDataStoreStub } from "../storage/UserDataStoreStub.spec";
+
+describe("regulation/Regulator", function () {
+  const USER1 = "USER1";
+  const USER2 = "USER2";
+  let userDataStoreStub: UserDataStoreStub;
+
+  beforeEach(function () {
+    userDataStoreStub = new UserDataStoreStub();
+    const dataStore: { [userId: string]: { userId: string, date: Date, isAuthenticationSuccessful: boolean }[] } = {
+      [USER1]: [],
+      [USER2]: []
+    };
+
+    userDataStoreStub.saveAuthenticationTraceStub.callsFake(function (userId, isAuthenticationSuccessful) {
+      dataStore[userId].unshift({
+        userId: userId,
+        date: new Date(),
+        isAuthenticationSuccessful: isAuthenticationSuccessful,
+      });
+      return BluebirdPromise.resolve();
+    });
+
+    userDataStoreStub.retrieveLatestAuthenticationTracesStub.callsFake(function (userId, count) {
+      const ret = (dataStore[userId].length <= count) ? dataStore[userId] : dataStore[userId].slice(0, 3);
+      return BluebirdPromise.resolve(ret);
+    });
+  });
+
+  afterEach(function () {
+    MockDate.reset();
+  });
+
+  function markAuthenticationAt(regulator: Regulator, user: string, time: string, success: boolean) {
+    MockDate.set(time);
+    return regulator.mark(user, success);
+  }
+
+  it("should mark 2 authentication and regulate (accept)", function () {
+    const regulator = new Regulator(userDataStoreStub, 3, 10, 10);
+
+    return regulator.mark(USER1, false)
+      .then(function () {
+        return regulator.mark(USER1, true);
+      })
+      .then(function () {
+        return regulator.regulate(USER1);
+      });
+  });
+
+  it("should mark 3 authentications and regulate (reject)", function () {
+    const regulator = new Regulator(userDataStoreStub, 3, 10, 10);
+
+    return regulator.mark(USER1, false)
+      .then(function () {
+        return regulator.mark(USER1, false);
+      })
+      .then(function () {
+        return regulator.mark(USER1, false);
+      })
+      .then(function () {
+        return regulator.regulate(USER1);
+      })
+      .then(function () { return BluebirdPromise.reject(new Error("should not be here!")); })
+      .catch(exceptions.AuthenticationRegulationError, function () {
+        return BluebirdPromise.resolve();
+      });
+  });
+
+  it("should mark 1 failed, 1 successful and 1 failed authentications within minimum time and regulate (accept)", function () {
+    const regulator = new Regulator(userDataStoreStub, 3, 60, 30);
+
+    return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:00", false)
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:10", true);
+      })
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:20", false);
+      })
+      .then(function () {
+        return regulator.regulate(USER1);
+      })
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:30", false);
+      })
+      .then(function () {
+        return regulator.regulate(USER1);
+      })
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:39", false);
+      })
+      .then(function () {
+        return regulator.regulate(USER1);
+      })
+      .then(function () {
+        return BluebirdPromise.reject(new Error("should not be here!"));
+      },
+      function () {
+        return BluebirdPromise.resolve();
+      });
+  });
+
+  it("should regulate user if number of failures is greater than 3 in allowed time lapse", function () {
+    function markAuthentications(regulator: Regulator, user: string) {
+      return markAuthenticationAt(regulator, user, "1/2/2000 00:00:00", false)
+        .then(function () {
+          return markAuthenticationAt(regulator, user, "1/2/2000 00:00:45", false);
+        })
+        .then(function () {
+          return markAuthenticationAt(regulator, user, "1/2/2000 00:01:05", false);
+        })
+        .then(function () {
+          return regulator.regulate(user);
+        });
+    }
+
+    const regulator1 = new Regulator(userDataStoreStub, 3, 60, 60);
+    const regulator2 = new Regulator(userDataStoreStub, 3, 2 * 60, 60);
+
+    const p1 = markAuthentications(regulator1, USER1);
+    const p2 = markAuthentications(regulator2, USER2);
+
+    return BluebirdPromise.join(p1, p2)
+      .then(function () {
+        return BluebirdPromise.reject(new Error("should not be here..."));
+      }, function () {
+        Assert(p1.isFulfilled());
+        Assert(p2.isRejected());
+      });
+  });
+
+  it("should user wait after regulation to authenticate again", function () {
+    function markAuthentications(regulator: Regulator, user: string) {
+      return markAuthenticationAt(regulator, user, "1/2/2000 00:00:00", false)
+        .then(function () {
+          return markAuthenticationAt(regulator, user, "1/2/2000 00:00:10", false);
+        })
+        .then(function () {
+          return markAuthenticationAt(regulator, user, "1/2/2000 00:00:15", false);
+        })
+        .then(function () {
+          return markAuthenticationAt(regulator, user, "1/2/2000 00:00:25", false);
+        })
+        .then(function () {
+          MockDate.set("1/2/2000 00:00:54");
+          return regulator.regulate(user);
+        })
+        .then(function () {
+          return BluebirdPromise.reject(new Error("should fail at this time"));
+        }, function () {
+          MockDate.set("1/2/2000 00:00:56");
+          return regulator.regulate(user);
+        });
+    }
+
+    const regulator = new Regulator(userDataStoreStub, 4, 30, 30);
+    return markAuthentications(regulator, USER1);
+  });
+
+  it("should disable regulation when max_retries is set to 0", function () {
+    const maxRetries = 0;
+    const regulator = new Regulator(userDataStoreStub, maxRetries, 60, 30);
+    return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:00", false)
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:10", false);
+      })
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:15", false);
+      })
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:25", false);
+      })
+      .then(function () {
+        MockDate.set("1/2/2000 00:00:26");
+        return regulator.regulate(USER1);
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/regulation/Regulator.ts b/themes/squares/server/src/lib/regulation/Regulator.ts
new file mode 100644
index 00000000..1037a6a1
--- /dev/null
+++ b/themes/squares/server/src/lib/regulation/Regulator.ts
@@ -0,0 +1,55 @@
+
+import * as BluebirdPromise from "bluebird";
+import exceptions = require("../Exceptions");
+import { IUserDataStore } from "../storage/IUserDataStore";
+import { AuthenticationTraceDocument } from "../storage/AuthenticationTraceDocument";
+import { IRegulator } from "./IRegulator";
+
+export class Regulator implements IRegulator {
+  private userDataStore: IUserDataStore;
+  private banTime: number;
+  private findTime: number;
+  private maxRetries: number;
+
+  constructor(userDataStore: any, maxRetries: number, findTime: number, banTime: number) {
+    this.userDataStore = userDataStore;
+    this.banTime = banTime;
+    this.findTime = findTime;
+    this.maxRetries = maxRetries;
+  }
+
+  // Mark authentication
+  mark(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
+    return this.userDataStore.saveAuthenticationTrace(userId, isAuthenticationSuccessful);
+  }
+
+  regulate(userId: string): BluebirdPromise<void> {
+    const that = this;
+
+    if (that.maxRetries <= 0) return BluebirdPromise.resolve();
+
+    return this.userDataStore.retrieveLatestAuthenticationTraces(userId, that.maxRetries)
+      .then((docs: AuthenticationTraceDocument[]) => {
+        // less than the max authorized number of authentication in time range, thus authorizing access
+        if (docs.length < that.maxRetries) return BluebirdPromise.resolve();
+
+        const numberOfFailedAuth = docs
+          .map(function (d: AuthenticationTraceDocument) { return d.isAuthenticationSuccessful == false ? 1 : 0; })
+          .reduce(function (acc, v) { return acc + v; }, 0);
+
+        if (numberOfFailedAuth < this.maxRetries) return BluebirdPromise.resolve();
+
+        const newestDocument = docs[0];
+        const oldestDocument = docs[that.maxRetries - 1];
+
+        const authenticationsTimeRangeInSeconds = (newestDocument.date.getTime() - oldestDocument.date.getTime()) / 1000;
+        const tooManyAuthInTimelapse = (authenticationsTimeRangeInSeconds < this.findTime);
+        const stillInBannedTimeRange = (new Date(new Date().getTime() - this.banTime * 1000) < newestDocument.date);
+
+        if (tooManyAuthInTimelapse && stillInBannedTimeRange)
+          throw new exceptions.AuthenticationRegulationError("Max number of authentication. Please retry in few minutes.");
+
+        return BluebirdPromise.resolve();
+      });
+  }
+}
diff --git a/themes/squares/server/src/lib/regulation/RegulatorStub.spec.ts b/themes/squares/server/src/lib/regulation/RegulatorStub.spec.ts
new file mode 100644
index 00000000..ca8a00fb
--- /dev/null
+++ b/themes/squares/server/src/lib/regulation/RegulatorStub.spec.ts
@@ -0,0 +1,22 @@
+import Bluebird = require("bluebird");
+import Sinon = require("sinon");
+import { IRegulator } from "./IRegulator";
+
+
+export class RegulatorStub implements IRegulator {
+    markStub: Sinon.SinonStub;
+    regulateStub: Sinon.SinonStub;
+
+    constructor() {
+        this.markStub = Sinon.stub();
+        this.regulateStub = Sinon.stub();
+    }
+
+    mark(userId: string, isAuthenticationSuccessful: boolean): Bluebird<void>  {
+        return this.markStub(userId, isAuthenticationSuccessful);
+    }
+
+    regulate(userId: string): Bluebird<void> {
+        return this.regulateStub(userId);
+    }
+}
diff --git a/themes/squares/server/src/lib/routes/error/401/get.spec.ts b/themes/squares/server/src/lib/routes/error/401/get.spec.ts
new file mode 100644
index 00000000..9fdac9c3
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/error/401/get.spec.ts
@@ -0,0 +1,61 @@
+import Sinon = require("sinon");
+import Express = require("express");
+import Assert = require("assert");
+import Get401 from "./get";
+import { ServerVariables } from "../../../ServerVariables";
+import { ServerVariablesMockBuilder, ServerVariablesMock }
+  from "../../../ServerVariablesMockBuilder.spec";
+
+describe("routes/error/401/get", function () {
+  let vars: ServerVariables;
+  let mocks: ServerVariablesMock;
+  let req: any;
+  let res: any;
+  let renderSpy: Sinon.SinonSpy;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    vars = s.variables;
+    mocks = s.mocks;
+
+    renderSpy = Sinon.spy();
+    req = {
+      headers: {}
+    };
+    res = {
+      render: renderSpy
+    };
+  });
+
+  it("should set redirection url to the default redirection url", function () {
+    vars.config.default_redirection_url = "http://default-redirection";
+    return Get401(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/401", {
+          redirection_url: "http://default-redirection"
+        }));
+      });
+  });
+
+  it("should set redirection url to the referer", function () {
+    req.headers["referer"] = "http://redirection";
+    return Get401(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/401", {
+          redirection_url: "http://redirection"
+        }));
+      });
+  });
+
+  it("should render without redirecting the user", function () {
+    return Get401(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/401", {
+          redirection_url: undefined
+        }));
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/error/401/get.ts b/themes/squares/server/src/lib/routes/error/401/get.ts
new file mode 100644
index 00000000..ca4a3963
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/error/401/get.ts
@@ -0,0 +1,15 @@
+
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import redirector from "../redirector";
+import { ServerVariables } from "../../../ServerVariables";
+
+export default function (vars: ServerVariables) {
+  return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
+    const redirectionUrl = redirector(req, vars);
+    res.render("errors/401", {
+      redirection_url: redirectionUrl
+    });
+    return BluebirdPromise.resolve();
+  };
+}
diff --git a/themes/squares/server/src/lib/routes/error/403/get.spec.ts b/themes/squares/server/src/lib/routes/error/403/get.spec.ts
new file mode 100644
index 00000000..22eb8485
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/error/403/get.spec.ts
@@ -0,0 +1,61 @@
+import Sinon = require("sinon");
+import Express = require("express");
+import Assert = require("assert");
+import Get403 from "./get";
+import { ServerVariables } from "../../../ServerVariables";
+import { ServerVariablesMockBuilder, ServerVariablesMock }
+  from "../../../ServerVariablesMockBuilder.spec";
+
+describe("routes/error/403/get", function () {
+  let vars: ServerVariables;
+  let mocks: ServerVariablesMock;
+  let req: any;
+  let res: any;
+  let renderSpy: Sinon.SinonSpy;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    vars = s.variables;
+    mocks = s.mocks;
+
+    renderSpy = Sinon.spy();
+    req = {
+      headers: {}
+    };
+    res = {
+      render: renderSpy
+    };
+  });
+
+  it("should set redirection url to the default redirection url", function () {
+    vars.config.default_redirection_url = "http://default-redirection";
+    return Get403(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/403", {
+          redirection_url: "http://default-redirection"
+        }));
+      });
+  });
+
+  it("should set redirection url to the referer", function () {
+    req.headers["referer"] = "http://redirection";
+    return Get403(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/403", {
+          redirection_url: "http://redirection"
+        }));
+      });
+  });
+
+  it("should render without redirecting the user", function () {
+    return Get403(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/403", {
+          redirection_url: undefined
+        }));
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/error/403/get.ts b/themes/squares/server/src/lib/routes/error/403/get.ts
new file mode 100644
index 00000000..3ab0319e
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/error/403/get.ts
@@ -0,0 +1,15 @@
+
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import redirector from "../redirector";
+import { ServerVariables } from "../../../ServerVariables";
+
+export default function (vars: ServerVariables) {
+  return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
+    const redirectionUrl = redirector(req, vars);
+    res.render("errors/403", {
+      redirection_url: redirectionUrl
+    });
+    return BluebirdPromise.resolve();
+  };
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/error/404/get.spec.ts b/themes/squares/server/src/lib/routes/error/404/get.spec.ts
new file mode 100644
index 00000000..73e4e6ce
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/error/404/get.spec.ts
@@ -0,0 +1,19 @@
+import Sinon = require("sinon");
+import Express = require("express");
+import Assert = require("assert");
+import Get404 from "./get";
+
+describe("routes/error/404/get", function () {
+  it("should render the page", function () {
+    const req = {} as Express.Request;
+    const res = {
+      render: Sinon.stub()
+    };
+
+    return Get404(req, res as any)
+      .then(function () {
+        Assert(res.render.calledOnce);
+        Assert(res.render.calledWith("errors/404"));
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/error/404/get.ts b/themes/squares/server/src/lib/routes/error/404/get.ts
new file mode 100644
index 00000000..6693b6fc
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/error/404/get.ts
@@ -0,0 +1,8 @@
+
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+
+export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
+    res.render("errors/404");
+    return BluebirdPromise.resolve();
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/error/redirector.ts b/themes/squares/server/src/lib/routes/error/redirector.ts
new file mode 100644
index 00000000..b1a3ccc1
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/error/redirector.ts
@@ -0,0 +1,13 @@
+import Express = require("express");
+import { ServerVariables } from "../../ServerVariables";
+
+export default function (req: Express.Request, vars: ServerVariables): string {
+  let redirectionUrl: string;
+
+  if (req.headers && req.headers["referer"])
+    redirectionUrl = "" + req.headers["referer"];
+  else if (vars.config.default_redirection_url)
+    redirectionUrl = vars.config.default_redirection_url;
+
+  return redirectionUrl;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/firstfactor/get.ts b/themes/squares/server/src/lib/routes/firstfactor/get.ts
new file mode 100644
index 00000000..d94f656c
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/firstfactor/get.ts
@@ -0,0 +1,72 @@
+
+import express = require("express");
+import Endpoints = require("../../../../../shared/api");
+import BluebirdPromise = require("bluebird");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import Constants = require("../../../../../shared/constants");
+import Util = require("util");
+import { ServerVariables } from "../../ServerVariables";
+import { SafeRedirector } from "../../utils/SafeRedirection";
+import { Level } from "../../authentication/Level";
+
+function getRedirectParam(
+  req: express.Request) {
+  return req.query[Constants.REDIRECT_QUERY_PARAM] != "undefined"
+    ? req.query[Constants.REDIRECT_QUERY_PARAM]
+    : undefined;
+}
+
+function redirectToSecondFactorPage(
+  req: express.Request,
+  res: express.Response) {
+
+  const redirectUrl = getRedirectParam(req);
+  if (!redirectUrl)
+    res.redirect(Endpoints.SECOND_FACTOR_GET);
+  else
+    res.redirect(
+      Util.format("%s?%s=%s",
+        Endpoints.SECOND_FACTOR_GET,
+        Constants.REDIRECT_QUERY_PARAM,
+        redirectUrl));
+}
+
+function redirectToService(
+  req: express.Request,
+  res: express.Response,
+  redirector: SafeRedirector) {
+  const redirectUrl = getRedirectParam(req);
+  if (!redirectUrl) {
+    res.redirect(Endpoints.LOGGED_IN);
+  } else {
+    redirector.redirectOrElse(res, redirectUrl, Endpoints.LOGGED_IN);
+  }
+}
+
+function renderFirstFactor(
+  res: express.Response) {
+
+  res.render("firstfactor", {
+    first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
+    reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET
+  });
+}
+
+export default function (
+  vars: ServerVariables) {
+
+  const redirector = new SafeRedirector(vars.config.session.domain);
+  return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
+    return new BluebirdPromise(function (resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      if (authSession.authentication_level == Level.ONE_FACTOR) {
+        redirectToSecondFactorPage(req, res);
+      } else if (authSession.authentication_level == Level.TWO_FACTOR) {
+        redirectToService(req, res, redirector);
+      } else {
+        renderFirstFactor(res);
+      }
+      resolve();
+    });
+  };
+}
diff --git a/themes/squares/server/src/lib/routes/firstfactor/post.spec.ts b/themes/squares/server/src/lib/routes/firstfactor/post.spec.ts
new file mode 100644
index 00000000..e1d078cd
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/firstfactor/post.spec.ts
@@ -0,0 +1,136 @@
+
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+import FirstFactorPost = require("./post");
+import exceptions = require("../../Exceptions");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../types/AuthenticationSession";
+import Endpoints = require("../../../../../shared/api");
+import AuthenticationRegulatorMock = require("../../regulation/RegulatorStub.spec");
+import ExpressMock = require("../../stubs/express.spec");
+import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../ServerVariables";
+
+describe("routes/firstfactor/post", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let emails: string[];
+  let groups: string[];
+  let vars: ServerVariables;
+  let mocks: ServerVariablesMock;
+  let authSession: AuthenticationSession;
+
+  beforeEach(function () {
+    emails = ["test_ok@example.com"];
+    groups = ["group1", "group2" ];
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    mocks.authorizer.authorizationMock.returns(true);
+    mocks.regulator.regulateStub.returns(BluebirdPromise.resolve());
+    mocks.regulator.markStub.returns(BluebirdPromise.resolve());
+
+    req = {
+      originalUrl: "/api/firstfactor",
+      body: {
+        username: "username",
+        password: "password"
+      },
+      query: {
+        redirect: "http://redirect.url"
+      },
+      session: {
+        cookie: {}
+      },
+      headers: {
+        host: "home.example.com"
+      }
+    };
+
+    res = ExpressMock.ResponseMock();
+    authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
+  });
+
+  it("should reply with 204 if success", function () {
+    mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
+      .returns(BluebirdPromise.resolve({
+        emails: emails,
+        groups: groups
+      }));
+    return FirstFactorPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal("username", authSession.userid);
+        Assert(res.send.calledOnce);
+      });
+  });
+
+  describe("keep me logged in", () => {
+    beforeEach(() => {
+      mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
+        .returns(BluebirdPromise.resolve({
+          emails: emails,
+          groups: groups
+        }));
+      req.body.keepMeLoggedIn = "true";
+      return FirstFactorPost.default(vars)(req as any, res as any);
+    });
+
+    it("should set keep_me_logged_in session variable to true", function () {
+      Assert.equal(authSession.keep_me_logged_in, true);
+    });
+
+    it("should set cookie maxAge to one year", function () {
+      Assert.equal(req.session.cookie.maxAge, 365 * 24 * 60 * 60 * 1000);
+    });
+  });
+
+  it("should retrieve email from LDAP", function () {
+    mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
+      .returns(BluebirdPromise.resolve([{ mail: ["test@example.com"] }]));
+    return FirstFactorPost.default(vars)(req as any, res as any);
+  });
+
+  it("should set first email address as user session variable", function () {
+    const emails = ["test_ok@example.com"];
+    mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
+      .returns(BluebirdPromise.resolve({
+        emails: emails,
+        groups: groups
+      }));
+
+      return FirstFactorPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal("test_ok@example.com", authSession.email);
+      });
+  });
+
+  it("should return error message when LDAP authenticator throws", function () {
+    mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
+      .returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials")));
+
+    return FirstFactorPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal(res.status.getCall(0).args[0], 200);
+        Assert.equal(mocks.regulator.markStub.getCall(0).args[0], "username");
+        Assert.deepEqual(res.send.getCall(0).args[0], {
+          error: "Operation failed."
+        });
+      });
+  });
+
+  it("should return error message when regulator rejects authentication", function () {
+    const err = new exceptions.AuthenticationRegulationError("Authentication regulation...");
+    mocks.regulator.regulateStub.returns(BluebirdPromise.reject(err));
+    return FirstFactorPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal(res.status.getCall(0).args[0], 200);
+        Assert.deepEqual(res.send.getCall(0).args[0], {
+          error: "Operation failed."
+        });
+      });
+  });
+});
+
+
diff --git a/themes/squares/server/src/lib/routes/firstfactor/post.ts b/themes/squares/server/src/lib/routes/firstfactor/post.ts
new file mode 100644
index 00000000..565681d6
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/firstfactor/post.ts
@@ -0,0 +1,101 @@
+
+import Exceptions = require("../../Exceptions");
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import Endpoint = require("../../../../../shared/api");
+import ErrorReplies = require("../../ErrorReplies");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import Constants = require("../../../../../shared/constants");
+import UserMessages = require("../../../../../shared/UserMessages");
+import { ServerVariables } from "../../ServerVariables";
+import { AuthenticationSession } from "../../../../types/AuthenticationSession";
+import { GroupsAndEmails } from "../../authentication/backends/GroupsAndEmails";
+import { Level as AuthenticationLevel } from "../../authentication/Level";
+import { Level as AuthorizationLevel } from "../../authorization/Level";
+import { URLDecomposer } from "../../utils/URLDecomposer";
+
+export default function (vars: ServerVariables) {
+  return function (req: express.Request, res: express.Response)
+    : BluebirdPromise<void> {
+    const username: string = req.body.username;
+    const password: string = req.body.password;
+    const keepMeLoggedIn: boolean = req.body.keepMeLoggedIn &&
+      req.body.keepMeLoggedIn === "true";
+    let authSession: AuthenticationSession;
+
+    if (keepMeLoggedIn) {
+      // Stay connected for 1 year.
+      vars.logger.debug(req, "User requested to stay logged in for one year.");
+      req.session.cookie.maxAge = 365 * 24 * 60 * 60 * 1000;
+    }
+
+    return BluebirdPromise.resolve()
+      .then(function () {
+        if (!username || !password) {
+          return BluebirdPromise.reject(new Error("No username or password."));
+        }
+        vars.logger.info(req, "Starting authentication of user \"%s\"", username);
+        authSession = AuthenticationSessionHandler.get(req, vars.logger);
+        return vars.regulator.regulate(username);
+      })
+      .then(function () {
+        vars.logger.info(req, "No regulation applied.");
+        return vars.usersDatabase.checkUserPassword(username, password);
+      })
+      .then(function (groupsAndEmails: GroupsAndEmails) {
+        vars.logger.info(req,
+          "LDAP binding successful. Retrieved information about user are %s",
+          JSON.stringify(groupsAndEmails));
+        authSession.userid = username;
+        authSession.keep_me_logged_in = keepMeLoggedIn;
+        authSession.authentication_level = AuthenticationLevel.ONE_FACTOR;
+        const redirectUrl: string = req.query[Constants.REDIRECT_QUERY_PARAM] !== "undefined"
+          // Fuck, don't know why it is a string!
+          ? req.query[Constants.REDIRECT_QUERY_PARAM]
+          : "";
+
+        const emails: string[] = groupsAndEmails.emails;
+        const groups: string[] = groupsAndEmails.groups;
+        const decomposition = URLDecomposer.fromUrl(redirectUrl);
+        const authorizationLevel = (decomposition)
+          ? vars.authorizer.authorization(
+            {domain: decomposition.domain, resource: decomposition.path},
+            {user: username, groups: groups})
+          : AuthorizationLevel.TWO_FACTOR;
+
+        if (emails.length > 0)
+          authSession.email = emails[0];
+        authSession.groups = groups;
+
+        vars.logger.debug(req, "Mark successful authentication to regulator.");
+        vars.regulator.mark(username, true);
+
+        if (authorizationLevel <= AuthorizationLevel.ONE_FACTOR) {
+          let newRedirectionUrl: string = redirectUrl;
+          if (!newRedirectionUrl)
+            newRedirectionUrl = Endpoint.LOGGED_IN;
+          res.send({
+            redirect: newRedirectionUrl
+          });
+          vars.logger.debug(req, "Redirect to '%s'", redirectUrl);
+        }
+        else {
+          let newRedirectUrl = Endpoint.SECOND_FACTOR_GET;
+          if (redirectUrl) {
+            newRedirectUrl += "?" + Constants.REDIRECT_QUERY_PARAM + "="
+              + redirectUrl;
+          }
+          vars.logger.debug(req, "Redirect to '%s'", newRedirectUrl);
+          res.send({
+            redirect: newRedirectUrl
+          });
+        }
+        return BluebirdPromise.resolve();
+      })
+      .catch(Exceptions.LdapBindError, function (err: Error) {
+        vars.regulator.mark(username, false);
+        return ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.OPERATION_FAILED)(err);
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.OPERATION_FAILED));
+  };
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/loggedin/get.ts b/themes/squares/server/src/lib/routes/loggedin/get.ts
new file mode 100644
index 00000000..283a041b
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/loggedin/get.ts
@@ -0,0 +1,23 @@
+import Express = require("express");
+import Endpoints = require("../../../../../shared/api");
+import BluebirdPromise = require("bluebird");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import { ServerVariables } from "../../ServerVariables";
+import ErrorReplies = require("../../ErrorReplies");
+
+export default function (vars: ServerVariables) {
+  function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
+    return new BluebirdPromise<void>(function (resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      res.render("already-logged-in", {
+        logout_endpoint: Endpoints.LOGOUT_GET,
+        username: authSession.userid,
+        redirection_url: vars.config.default_redirection_url
+      });
+      resolve();
+    })
+      .catch(ErrorReplies.replyWithError401(req, res, vars.logger));
+  }
+
+  return handler;
+}
diff --git a/themes/squares/server/src/lib/routes/logout/get.ts b/themes/squares/server/src/lib/routes/logout/get.ts
new file mode 100644
index 00000000..4d511214
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/logout/get.ts
@@ -0,0 +1,20 @@
+
+import express = require("express");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import Constants = require("../../../../../shared/constants");
+import { ServerVariables } from "../../ServerVariables";
+
+function getRedirectParam(req: express.Request) {
+  return req.query[Constants.REDIRECT_QUERY_PARAM] != "undefined"
+    ? req.query[Constants.REDIRECT_QUERY_PARAM]
+    : undefined;
+}
+
+export default function (vars: ServerVariables) {
+  return function(req: express.Request, res: express.Response) {
+    const redirect_param = getRedirectParam(req);
+    const redirect_url = redirect_param || "/";
+    AuthenticationSessionHandler.reset(req);
+    res.redirect(redirect_url);
+  };
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/password-reset/constants.ts b/themes/squares/server/src/lib/routes/password-reset/constants.ts
new file mode 100644
index 00000000..5c639e92
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/password-reset/constants.ts
@@ -0,0 +1,2 @@
+
+export const CHALLENGE = "reset-password";
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/password-reset/form/post.spec.ts b/themes/squares/server/src/lib/routes/password-reset/form/post.spec.ts
new file mode 100644
index 00000000..ed029c90
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/password-reset/form/post.spec.ts
@@ -0,0 +1,122 @@
+
+import PasswordResetFormPost = require("./post");
+import { AuthenticationSessionHandler } from "../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
+import { UserDataStore } from "../../../storage/UserDataStore";
+import Sinon = require("sinon");
+import Assert = require("assert");
+import BluebirdPromise = require("bluebird");
+import ExpressMock = require("../../../stubs/express.spec");
+import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../ServerVariables";
+import { Level } from "../../../authentication/Level";
+
+describe("routes/password-reset/form/post", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let vars: ServerVariables;
+  let mocks: ServerVariablesMock;
+  let authSession: AuthenticationSession;
+
+  beforeEach(function () {
+    req = {
+      originalUrl: "/api/password-reset",
+      body: {
+        userid: "user"
+      },
+      session: {},
+      headers: {
+        host: "localhost"
+      }
+    };
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
+
+    mocks.config.authentication_backend.ldap = {
+      url: "ldap://ldapjs",
+      mail_attribute: "mail",
+      user: "user",
+      password: "password",
+      additional_users_dn: "ou=users",
+      additional_groups_dn: "ou=groups",
+      base_dn: "dc=example,dc=com",
+      users_filter: "user",
+      group_name_attribute: "cn",
+      groups_filter: "groups"
+    };
+
+    res = ExpressMock.ResponseMock();
+    authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
+    authSession.userid = "user";
+    authSession.email = "user@example.com";
+    authSession.authentication_level = Level.ONE_FACTOR;
+  });
+
+  describe("test reset password post", () => {
+    it("should update the password and reset auth_session for reauthentication", function () {
+      req.body = {};
+      req.body.password = "new-password";
+
+      mocks.usersDatabase.updatePasswordStub.returns(BluebirdPromise.resolve());
+
+      authSession.identity_check = {
+        userid: "user",
+        challenge: "reset-password"
+      };
+      return PasswordResetFormPost.default(vars)(req as any, res as any)
+        .then(function () {
+          return AuthenticationSessionHandler.get(req as any, vars.logger);
+        }).then(function (_authSession) {
+          Assert.equal(res.status.getCall(0).args[0], 204);
+          Assert.equal(_authSession.authentication_level, Level.NOT_AUTHENTICATED);
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail if identity_challenge does not exist", function () {
+      authSession.identity_check = {
+        userid: "user",
+        challenge: undefined
+      };
+      return PasswordResetFormPost.default(vars)(req as any, res as any)
+        .then(function () {
+          Assert.equal(res.status.getCall(0).args[0], 200);
+          Assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "An error occurred during password reset. Your password has not been changed."
+          });
+        });
+    });
+
+    it("should fail when ldap fails", function () {
+      req.body = {};
+      req.body.password = "new-password";
+
+      mocks.usersDatabase.updatePasswordStub
+        .returns(BluebirdPromise.reject("Internal error with LDAP"));
+
+      authSession.identity_check = {
+        challenge: "reset-password",
+        userid: "user"
+      };
+      return PasswordResetFormPost.default(vars)(req as any, res as any)
+        .then(function () {
+          Assert.equal(res.status.getCall(0).args[0], 200);
+          Assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "An error occurred during password reset. Your password has not been changed."
+          });
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+});
diff --git a/themes/squares/server/src/lib/routes/password-reset/form/post.ts b/themes/squares/server/src/lib/routes/password-reset/form/post.ts
new file mode 100644
index 00000000..fccd7471
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/password-reset/form/post.ts
@@ -0,0 +1,50 @@
+
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import objectPath = require("object-path");
+import exceptions = require("../../../Exceptions");
+import { AuthenticationSessionHandler } from "../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
+import ErrorReplies = require("../../../ErrorReplies");
+import UserMessages = require("../../../../../../shared/UserMessages");
+import { ServerVariables } from "../../../ServerVariables";
+
+import Constants = require("./../constants");
+
+export default function (vars: ServerVariables) {
+  return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    const newPassword = objectPath.get<express.Request, string>(req, "body.password");
+
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      if (!authSession.identity_check) {
+        reject(new Error("No identity check initiated"));
+        return;
+      }
+
+      vars.logger.info(req, "User %s wants to reset his/her password.",
+        authSession.identity_check.userid);
+      vars.logger.debug(req, "Challenge %s", authSession.identity_check.challenge);
+
+      if (authSession.identity_check.challenge != Constants.CHALLENGE) {
+        reject(new Error("Bad challenge."));
+        return;
+      }
+      resolve();
+    })
+      .then(function () {
+        return vars.usersDatabase.updatePassword(authSession.identity_check.userid, newPassword);
+      })
+      .then(function () {
+        vars.logger.info(req, "Password reset for user '%s'",
+          authSession.identity_check.userid);
+        AuthenticationSessionHandler.reset(req);
+        res.status(204);
+        res.send();
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.RESET_PASSWORD_FAILED));
+  };
+}
diff --git a/themes/squares/server/src/lib/routes/password-reset/identity/PasswordResetHandler.spec.ts b/themes/squares/server/src/lib/routes/password-reset/identity/PasswordResetHandler.spec.ts
new file mode 100644
index 00000000..ac6a4175
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/password-reset/identity/PasswordResetHandler.spec.ts
@@ -0,0 +1,92 @@
+
+import PasswordResetHandler
+  from "./PasswordResetHandler";
+import { UserDataStore } from "../../../storage/UserDataStore";
+import Sinon = require("sinon");
+import winston = require("winston");
+import assert = require("assert");
+import BluebirdPromise = require("bluebird");
+import ExpressMock = require("../../../stubs/express.spec");
+import { ServerVariablesMock, ServerVariablesMockBuilder }
+  from "../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../ServerVariables";
+
+describe("routes/password-reset/identity/PasswordResetHandler", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    req = {
+      originalUrl: "/non-api/xxx",
+      query: {
+        userid: "user"
+      },
+      session: {
+        auth: {
+          userid: "user",
+          email: "user@example.com",
+          first_factor: true,
+          second_factor: false
+        }
+      },
+      headers: {
+        host: "localhost"
+      }
+    };
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    mocks.userDataStore.saveU2FRegistrationStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.produceIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.consumeIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve({}));
+    res = ExpressMock.ResponseMock();
+  });
+
+  describe("test reset password identity pre check", () => {
+    it("should fail when no userid is provided", function () {
+      req.query.userid = undefined;
+      const handler = new PasswordResetHandler(vars.logger,
+        vars.usersDatabase);
+      return handler.preValidationInit(req as any)
+        .then(function () {
+          return BluebirdPromise.reject("It should fail");
+        })
+        .catch(function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail if ldap fail", function () {
+      mocks.usersDatabase.getEmailsStub
+        .returns(BluebirdPromise.reject("Internal error"));
+      new PasswordResetHandler(vars.logger, vars.usersDatabase)
+        .preValidationInit(req as any)
+        .then(function () {
+          return BluebirdPromise.reject(new Error("should not be here"));
+        },
+        function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should returns identity when ldap replies", function () {
+      mocks.usersDatabase.getEmailsStub
+        .returns(BluebirdPromise.resolve(["test@example.com"]));
+      return new PasswordResetHandler(vars.logger, vars.usersDatabase)
+        .preValidationInit(req as any);
+    });
+  });
+});
diff --git a/themes/squares/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts b/themes/squares/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts
new file mode 100644
index 00000000..42ae92cd
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts
@@ -0,0 +1,69 @@
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import objectPath = require("object-path");
+
+import exceptions = require("../../../Exceptions");
+import { Identity } from "../../../../../types/Identity";
+import { IdentityValidable } from "../../../IdentityValidable";
+import { PRE_VALIDATION_TEMPLATE } from "../../../IdentityCheckPreValidationTemplate";
+import Constants = require("../constants");
+import { IRequestLogger } from "../../../logging/IRequestLogger";
+import { IUsersDatabase } from "../../../authentication/backends/IUsersDatabase";
+
+export const TEMPLATE_NAME = "password-reset-form";
+
+export default class PasswordResetHandler implements IdentityValidable {
+  private logger: IRequestLogger;
+  private usersDatabase: IUsersDatabase;
+
+  constructor(logger: IRequestLogger, usersDatabase: IUsersDatabase) {
+    this.logger = logger;
+    this.usersDatabase = usersDatabase;
+  }
+
+  challenge(): string {
+    return Constants.CHALLENGE;
+  }
+
+  preValidationInit(req: express.Request): BluebirdPromise<Identity> {
+    const that = this;
+    const userid: string =
+      objectPath.get<express.Request, string>(req, "query.userid");
+    return BluebirdPromise.resolve()
+      .then(function () {
+        that.logger.debug(req, "User '%s' requested a password reset", userid);
+        if (!userid)
+          return BluebirdPromise.reject(
+            new exceptions.AccessDeniedError("No user id provided"));
+
+        return that.usersDatabase.getEmails(userid);
+      })
+      .then(function (emails: string[]) {
+        if (!emails && emails.length <= 0) throw new Error("No email found");
+        const identity = {
+          email: emails[0],
+          userid: userid
+        };
+        return BluebirdPromise.resolve(identity);
+      })
+      .catch(function (err: Error) {
+        return BluebirdPromise.reject(new exceptions.IdentityError(err.message));
+      });
+  }
+
+  preValidationResponse(req: express.Request, res: express.Response) {
+    res.render(PRE_VALIDATION_TEMPLATE);
+  }
+
+  postValidationInit(req: express.Request) {
+    return BluebirdPromise.resolve();
+  }
+
+  postValidationResponse(req: express.Request, res: express.Response) {
+    res.render(TEMPLATE_NAME);
+  }
+
+  mailSubject(): string {
+    return "Reset your password";
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/password-reset/request/get.ts b/themes/squares/server/src/lib/routes/password-reset/request/get.ts
new file mode 100644
index 00000000..8f3ae2b4
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/password-reset/request/get.ts
@@ -0,0 +1,13 @@
+
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import objectPath = require("object-path");
+import exceptions = require("../../../Exceptions");
+
+import Constants = require("./../constants");
+
+const TEMPLATE_NAME = "password-reset-request";
+
+export default function (req: express.Request, res: express.Response) {
+    res.render(TEMPLATE_NAME);
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/secondfactor/get.spec.ts b/themes/squares/server/src/lib/routes/secondfactor/get.spec.ts
new file mode 100644
index 00000000..6c77e1f6
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/get.spec.ts
@@ -0,0 +1,44 @@
+import SecondFactorGet from "./get";
+import { ServerVariablesMockBuilder, ServerVariablesMock }
+  from "../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../ServerVariables";
+import Sinon = require("sinon");
+import ExpressMock = require("../../stubs/express.spec");
+import Assert = require("assert");
+import Endpoints = require("../../../../../shared/api");
+import BluebirdPromise = require("bluebird");
+
+describe("routes/secondfactor/get", function () {
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req = ExpressMock.RequestMock();
+    res = ExpressMock.ResponseMock();
+
+    req.session = {
+      auth: {
+        userid: "user",
+        first_factor: true,
+        second_factor: false
+      }
+    };
+  });
+
+  describe("test rendering", function () {
+    it("should render second factor page", function () {
+      req.session.auth.second_factor = false;
+      return SecondFactorGet(vars)(req as any, res as any)
+        .then(function () {
+          Assert(res.render.calledWith("secondfactor"));
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/secondfactor/get.ts b/themes/squares/server/src/lib/routes/secondfactor/get.ts
new file mode 100644
index 00000000..9f6deb4c
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/get.ts
@@ -0,0 +1,28 @@
+
+import Express = require("express");
+import Endpoints = require("../../../../../shared/api");
+import BluebirdPromise = require("bluebird");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import { ServerVariables } from "../../ServerVariables";
+
+const TEMPLATE_NAME = "secondfactor";
+
+export default function (vars: ServerVariables) {
+  function handler(req: Express.Request, res: Express.Response)
+    : BluebirdPromise<void> {
+
+    return new BluebirdPromise(function (resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, vars.logger);
+
+      res.render(TEMPLATE_NAME, {
+        username: authSession.userid,
+        totp_identity_start_endpoint:
+        Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
+        u2f_identity_start_endpoint:
+        Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET
+      });
+      resolve();
+    });
+  }
+  return handler;
+}
diff --git a/themes/squares/server/src/lib/routes/secondfactor/redirect.spec.ts b/themes/squares/server/src/lib/routes/secondfactor/redirect.spec.ts
new file mode 100644
index 00000000..ea66e6dc
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/redirect.spec.ts
@@ -0,0 +1,41 @@
+import Redirect from "./redirect";
+import ExpressMock = require("../../stubs/express.spec");
+import { ServerVariablesMockBuilder, ServerVariablesMock }
+from "../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../ServerVariables";
+import Assert = require("assert");
+
+describe("routes/secondfactor/redirect", function() {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req = ExpressMock.RequestMock();
+    res = ExpressMock.ResponseMock();
+  });
+
+  it("should redirect to default_redirection_url", function() {
+    vars.config.default_redirection_url = "http://default_redirection_url";
+    Redirect(vars)(req as any, res as any)
+    .then(function() {
+      Assert(res.json.calledWith({
+        redirect: "http://default_redirection_url"
+      }));
+    });
+  });
+
+  it("should redirect to /", function() {
+    Redirect(vars)(req as any, res as any)
+    .then(function() {
+      Assert(res.json.calledWith({
+        redirect: "/"
+      }));
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/secondfactor/redirect.ts b/themes/squares/server/src/lib/routes/secondfactor/redirect.ts
new file mode 100644
index 00000000..5d84d9eb
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/redirect.ts
@@ -0,0 +1,30 @@
+
+import express = require("express");
+import objectPath = require("object-path");
+import Endpoints = require("../../../../../shared/api");
+import { ServerVariables } from "../../ServerVariables";
+import BluebirdPromise = require("bluebird");
+import ErrorReplies = require("../../ErrorReplies");
+import UserMessages = require("../../../../../shared/UserMessages");
+import { RedirectionMessage } from "../../../../../shared/RedirectionMessage";
+import Constants = require("../../../../../shared/constants");
+
+export default function (vars: ServerVariables) {
+  return function (req: express.Request, res: express.Response)
+    : BluebirdPromise<void> {
+
+    return new BluebirdPromise<void>(function (resolve, reject) {
+      let redirectUrl: string = "/";
+      if (vars.config.default_redirection_url) {
+        redirectUrl = vars.config.default_redirection_url;
+      }
+      vars.logger.debug(req, "Request redirection to \"%s\".", redirectUrl);
+      res.json({
+        redirect: redirectUrl
+      } as RedirectionMessage);
+      return resolve();
+    })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  };
+}
diff --git a/themes/squares/server/src/lib/routes/secondfactor/totp/constants.ts b/themes/squares/server/src/lib/routes/secondfactor/totp/constants.ts
new file mode 100644
index 00000000..7b5a1dcf
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/totp/constants.ts
@@ -0,0 +1,4 @@
+
+export const CHALLENGE = "totp-register";
+export const TEMPLATE_NAME = "totp-register";
+
diff --git a/themes/squares/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.spec.ts b/themes/squares/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.spec.ts
new file mode 100644
index 00000000..78b8ea3e
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.spec.ts
@@ -0,0 +1,116 @@
+import Sinon = require("sinon");
+import RegistrationHandler from "./RegistrationHandler";
+import { Identity } from "../../../../../../types/Identity";
+import { UserDataStore } from "../../../../storage/UserDataStore";
+import BluebirdPromise = require("bluebird");
+import ExpressMock = require("../../../../stubs/express.spec");
+import { ServerVariablesMock, ServerVariablesMockBuilder }
+  from "../../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../../ServerVariables";
+import Assert = require("assert");
+
+describe("routes/secondfactor/totp/identity/RegistrationHandler", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req = ExpressMock.RequestMock();
+    req.session = {
+      auth: {
+        userid: "user",
+        email: "user@example.com",
+        first_factor: true,
+        second_factor: false,
+        identity_check: {
+          userid: "user",
+          challenge: "totp-register"
+        }
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    mocks.userDataStore.saveU2FRegistrationStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.produceIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.consumeIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.saveTOTPSecretStub
+      .returns(BluebirdPromise.resolve({}));
+
+    res = ExpressMock.ResponseMock();
+  });
+
+  describe("test totp registration pre validation", function () {
+    it("should fail if first_factor has not been passed", function () {
+      req.session.auth.first_factor = false;
+      return new RegistrationHandler(vars.logger, vars.userDataStore,
+        vars.totpHandler, vars.config.totp)
+        .preValidationInit(req as any)
+        .then(function () {
+          return BluebirdPromise.reject(new Error("It should fail"));
+        })
+        .catch(function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail if userid is missing", function (done) {
+      req.session.auth.first_factor = false;
+      req.session.auth.userid = undefined;
+
+      new RegistrationHandler(vars.logger, vars.userDataStore, vars.totpHandler,
+        vars.config.totp)
+        .preValidationInit(req as any)
+        .catch(function (err: Error) {
+          done();
+        });
+    });
+
+    it("should fail if email is missing", function (done) {
+      req.session.auth.first_factor = false;
+      req.session.auth.email = undefined;
+
+      new RegistrationHandler(vars.logger, vars.userDataStore, vars.totpHandler,
+        vars.config.totp)
+        .preValidationInit(req as any)
+        .catch(function (err: Error) {
+          done();
+        });
+    });
+
+    it("should succeed if first factor passed, userid and email are provided",
+      function () {
+        return new RegistrationHandler(vars.logger, vars.userDataStore,
+          vars.totpHandler, vars.config.totp)
+          .preValidationInit(req as any);
+      });
+  });
+
+  describe("test totp registration post validation", function () {
+    it("should generate a secret using userId as label and issuer defined in config", function () {
+      vars.config.totp = {
+        issuer: "issuer"
+      };
+      return new RegistrationHandler(vars.logger, vars.userDataStore,
+        vars.totpHandler, vars.config.totp)
+        .postValidationResponse(req as any, res as any)
+        .then(function() {
+          Assert(mocks.totpHandler.generateStub.calledWithExactly("user", "issuer"));
+        });
+    });
+  });
+});
diff --git a/themes/squares/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts b/themes/squares/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts
new file mode 100644
index 00000000..b39b6d04
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts
@@ -0,0 +1,112 @@
+
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import objectPath = require("object-path");
+
+import { Identity } from "../../../../../../types/Identity";
+import { IdentityValidable } from "../../../../IdentityValidable";
+import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationTemplate";
+import Constants = require("../constants");
+import Endpoints = require("../../../../../../../shared/api");
+import ErrorReplies = require("../../../../ErrorReplies");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import FirstFactorValidator = require("../../../../FirstFactorValidator");
+import { IRequestLogger } from "../../../../logging/IRequestLogger";
+import { IUserDataStore } from "../../../../storage/IUserDataStore";
+import { ITotpHandler } from "../../../../authentication/totp/ITotpHandler";
+import { TOTPSecret } from "../../../../../../types/TOTPSecret";
+import { TotpConfiguration } from "../../../../configuration/schema/TotpConfiguration";
+
+
+export default class RegistrationHandler implements IdentityValidable {
+  private logger: IRequestLogger;
+  private userDataStore: IUserDataStore;
+  private totp: ITotpHandler;
+  private configuration: TotpConfiguration;
+
+  constructor(logger: IRequestLogger,
+    userDataStore: IUserDataStore,
+    totp: ITotpHandler, configuration: TotpConfiguration) {
+    this.logger = logger;
+    this.userDataStore = userDataStore;
+    this.totp = totp;
+    this.configuration = configuration;
+  }
+
+  challenge(): string {
+    return Constants.CHALLENGE;
+  }
+
+  private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
+    const that = this;
+    return new BluebirdPromise(function (resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, that.logger);
+      const userid = authSession.userid;
+      const email = authSession.email;
+
+      if (!(userid && email)) {
+        return reject(new Error("User ID or email is missing"));
+      }
+
+      const identity = {
+        email: email,
+        userid: userid
+      };
+      return resolve(identity);
+    });
+  }
+
+  preValidationInit(req: express.Request): BluebirdPromise<Identity> {
+    const that = this;
+    return FirstFactorValidator.validate(req, this.logger)
+      .then(function () {
+        return that.retrieveIdentity(req);
+      });
+  }
+
+  preValidationResponse(req: express.Request, res: express.Response) {
+    res.render(PRE_VALIDATION_TEMPLATE);
+  }
+
+  postValidationInit(req: express.Request) {
+    return FirstFactorValidator.validate(req, this.logger);
+  }
+
+  postValidationResponse(req: express.Request, res: express.Response)
+    : BluebirdPromise<void> {
+    const that = this;
+    let secret: TOTPSecret;
+    let userId: string;
+    return new BluebirdPromise(function (resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, that.logger);
+      userId = authSession.userid;
+
+      if (authSession.identity_check.challenge != Constants.CHALLENGE
+        || !userId)
+        return reject(new Error("Bad challenge."));
+
+      resolve();
+    })
+      .then(function () {
+        secret = that.totp.generate(userId,
+          that.configuration.issuer);
+        that.logger.debug(req, "Save the TOTP secret in DB");
+        return that.userDataStore.saveTOTPSecret(userId, secret);
+      })
+      .then(function () {
+        AuthenticationSessionHandler.reset(req);
+
+        res.render(Constants.TEMPLATE_NAME, {
+          base32_secret: secret.base32,
+          otpauth_url: secret.otpauth_url,
+          login_endpoint: Endpoints.FIRST_FACTOR_GET
+        });
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, that.logger, UserMessages.OPERATION_FAILED));
+  }
+
+  mailSubject(): string {
+    return "Set up Authelia's one-time password";
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/secondfactor/totp/sign/post.spec.ts b/themes/squares/server/src/lib/routes/secondfactor/totp/sign/post.spec.ts
new file mode 100644
index 00000000..70a20d39
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/totp/sign/post.spec.ts
@@ -0,0 +1,76 @@
+
+import BluebirdPromise = require("bluebird");
+import Sinon = require("sinon");
+import Assert = require("assert");
+import Exceptions = require("../../../../Exceptions");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+import SignPost = require("./post");
+import { ServerVariables } from "../../../../ServerVariables";
+
+import ExpressMock = require("../../../../stubs/express.spec");
+import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
+import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec";
+import { Level } from "../../../../authentication/Level";
+
+describe("routes/secondfactor/totp/sign/post", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let authSession: AuthenticationSession;
+  let vars: ServerVariables;
+  let mocks: ServerVariablesMock;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    vars = s.variables;
+    mocks = s.mocks;
+    const app_get = Sinon.stub();
+    req = {
+      originalUrl: "/api/totp-register",
+      app: {},
+      body: {
+        token: "abc"
+      },
+      session: {},
+      query: {
+        redirect: "http://redirect"
+      }
+    };
+    res = ExpressMock.ResponseMock();
+
+    const doc = {
+      userid: "user",
+      secret: {
+        base32: "ABCDEF"
+      }
+    };
+    mocks.userDataStore.retrieveTOTPSecretStub.returns(BluebirdPromise.resolve(doc));
+    authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
+    authSession.userid = "user";
+    authSession.authentication_level = Level.ONE_FACTOR;
+  });
+
+
+  it("should send status code 200 when totp is valid", function () {
+    mocks.totpHandler.validateStub.returns(true);
+    return SignPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal(authSession.authentication_level, Level.TWO_FACTOR);
+        return BluebirdPromise.resolve();
+      });
+  });
+
+  it("should send error message when totp is not valid", function () {
+    mocks.totpHandler.validateStub.returns(false);
+    return SignPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.notEqual(authSession.authentication_level, Level.TWO_FACTOR);
+        Assert.equal(res.status.getCall(0).args[0], 200);
+        Assert.deepEqual(res.send.getCall(0).args[0], {
+          error: "Operation failed."
+        });
+        return BluebirdPromise.resolve();
+      });
+  });
+});
+
diff --git a/themes/squares/server/src/lib/routes/secondfactor/totp/sign/post.ts b/themes/squares/server/src/lib/routes/secondfactor/totp/sign/post.ts
new file mode 100644
index 00000000..34a276d1
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/totp/sign/post.ts
@@ -0,0 +1,42 @@
+import Bluebird = require("bluebird");
+import Express = require("express");
+
+import { TOTPSecretDocument } from "../../../../storage/TOTPSecretDocument";
+import Endpoints = require("../../../../../../../shared/api");
+import Redirect from "../../redirect";
+import ErrorReplies = require("../../../../ErrorReplies");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import { ServerVariables } from "../../../../ServerVariables";
+import { Level } from "../../../../authentication/Level";
+
+const UNAUTHORIZED_MESSAGE = "Unauthorized access";
+
+export default function (vars: ServerVariables) {
+  function handler(req: Express.Request, res: Express.Response): Bluebird<void> {
+    let authSession: AuthenticationSession;
+    const token = req.body.token;
+
+    return new Bluebird(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      vars.logger.info(req, "Initiate TOTP validation for user \"%s\".", authSession.userid);
+      resolve();
+    })
+      .then(function () {
+        return vars.userDataStore.retrieveTOTPSecret(authSession.userid);
+      })
+      .then(function (doc: TOTPSecretDocument) {
+        if (!vars.totpHandler.validate(token, doc.secret.base32))
+          return Bluebird.reject(new Error("Invalid TOTP token."));
+
+        vars.logger.debug(req, "TOTP validation succeeded.");
+        authSession.authentication_level = Level.TWO_FACTOR;
+        Redirect(vars)(req, res);
+        return Bluebird.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  }
+  return handler;
+}
diff --git a/themes/squares/server/src/lib/routes/secondfactor/u2f/U2FCommon.ts b/themes/squares/server/src/lib/routes/secondfactor/u2f/U2FCommon.ts
new file mode 100644
index 00000000..7f16c0ee
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/u2f/U2FCommon.ts
@@ -0,0 +1,11 @@
+
+import util = require("util");
+import express = require("express");
+
+function extract_app_id(req: express.Request): string {
+  return util.format("https://%s", req.headers.host);
+}
+
+export = {
+  extract_app_id: extract_app_id
+};
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.spec.ts b/themes/squares/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.spec.ts
new file mode 100644
index 00000000..a54bfbfe
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.spec.ts
@@ -0,0 +1,96 @@
+import Sinon = require("sinon");
+import Assert = require("assert");
+import BluebirdPromise = require("bluebird");
+
+import { Identity } from "../../../../../../types/Identity";
+import RegistrationHandler from "./RegistrationHandler";
+import ExpressMock = require("../../../../stubs/express.spec");
+import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
+import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../../ServerVariables";
+
+describe("routes/secondfactor/u2f/identity/RegistrationHandler", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req = ExpressMock.RequestMock();
+    req.app = {};
+    req.session = {
+      auth: {
+        userid: "user",
+        email: "user@example.com",
+        first_factor: true,
+        second_factor: false
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
+
+    res = ExpressMock.ResponseMock();
+    res.send = Sinon.spy();
+    res.json = Sinon.spy();
+    res.status = Sinon.spy();
+  });
+
+  describe("test u2f registration check", test_registration_check);
+
+  function test_registration_check() {
+    it("should fail if first_factor has not been passed", function () {
+      req.session.auth.first_factor = false;
+      return new RegistrationHandler(vars.logger).preValidationInit(req as any)
+        .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
+        .catch(function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail if userid is missing", function () {
+      req.session.auth.first_factor = false;
+      req.session.auth.userid = undefined;
+
+      return new RegistrationHandler(vars.logger).preValidationInit(req as any)
+        .then(function () {
+          return BluebirdPromise.reject(new Error("should not be here"));
+        },
+        function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail if email is missing", function () {
+      req.session.auth.first_factor = false;
+      req.session.auth.email = undefined;
+
+      return new RegistrationHandler(vars.logger).preValidationInit(req as any)
+        .then(function () {
+          return BluebirdPromise.reject(new Error("should not be here"));
+        },
+        function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should succeed if first factor passed, userid and email are provided", function () {
+      req.session.auth.first_factor = true;
+      req.session.auth.email = "admin@example.com";
+      req.session.auth.userid = "user";
+      return new RegistrationHandler(vars.logger).preValidationInit(req as any);
+    });
+  }
+});
diff --git a/themes/squares/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts b/themes/squares/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts
new file mode 100644
index 00000000..bc4713c7
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts
@@ -0,0 +1,73 @@
+
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import objectPath = require("object-path");
+
+import { IdentityValidable } from "../../../../IdentityValidable";
+import { Identity } from "../../../../../../types/Identity";
+import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationTemplate";
+import FirstFactorValidator = require("../../../../FirstFactorValidator");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import { IRequestLogger } from "../../../../logging/IRequestLogger";
+
+const CHALLENGE = "u2f-register";
+const MAIL_SUBJECT = "Register your security key with Authelia";
+
+const POST_VALIDATION_TEMPLATE_NAME = "u2f-register";
+
+
+export default class RegistrationHandler implements IdentityValidable {
+  private logger: IRequestLogger;
+
+  constructor(logger: IRequestLogger) {
+    this.logger = logger;
+  }
+
+  challenge(): string {
+    return CHALLENGE;
+  }
+
+  private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
+    const that = this;
+    return new BluebirdPromise(function(resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, that.logger);
+      const userid = authSession.userid;
+      const email = authSession.email;
+
+      if (!(userid && email)) {
+        return reject(new Error("User ID or email is missing"));
+      }
+
+      const identity = {
+        email: email,
+        userid: userid
+      };
+      return resolve(identity);
+    });
+  }
+
+  preValidationInit(req: express.Request): BluebirdPromise<Identity> {
+    const that = this;
+    return FirstFactorValidator.validate(req, this.logger)
+      .then(function () {
+        return that.retrieveIdentity(req);
+      });
+  }
+
+  preValidationResponse(req: express.Request, res: express.Response) {
+    res.render(PRE_VALIDATION_TEMPLATE);
+  }
+
+  postValidationInit(req: express.Request) {
+    return FirstFactorValidator.validate(req, this.logger);
+  }
+
+  postValidationResponse(req: express.Request, res: express.Response) {
+    res.render(POST_VALIDATION_TEMPLATE_NAME);
+  }
+
+  mailSubject(): string {
+    return MAIL_SUBJECT;
+  }
+}
+
diff --git a/themes/squares/server/src/lib/routes/secondfactor/u2f/register/post.spec.ts b/themes/squares/server/src/lib/routes/secondfactor/u2f/register/post.spec.ts
new file mode 100644
index 00000000..de3347a2
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/u2f/register/post.spec.ts
@@ -0,0 +1,146 @@
+
+import sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import assert = require("assert");
+import U2FRegisterPost = require("./post");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+import ExpressMock = require("../../../../stubs/express.spec");
+import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
+import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../../ServerVariables";
+
+
+describe("routes/secondfactor/u2f/register/post", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+  let authSession: AuthenticationSession;
+
+  beforeEach(function () {
+    req = ExpressMock.RequestMock();
+    req.originalUrl = "/api/xxxx";
+    req.app = {};
+    req.session = {
+      auth: {
+        userid: "user",
+        first_factor: true,
+        second_factor: false,
+        identity_check: {
+          challenge: "u2f-register",
+          userid: "user"
+        }
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+
+    res = ExpressMock.ResponseMock();
+    res.send = sinon.spy();
+    res.json = sinon.spy();
+    res.status = sinon.spy();
+
+    authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
+  });
+
+  describe("test registration", test_registration);
+
+
+  function test_registration() {
+    it("should save u2f meta and return status code 200", function () {
+      const expectedStatus = {
+        keyHandle: "keyHandle",
+        publicKey: "pbk",
+        certificate: "cert"
+      };
+      mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve(expectedStatus));
+
+      authSession.register_request = {
+        appId: "app",
+        challenge: "challenge",
+        keyHandle: "key",
+        version: "U2F_V2"
+      };
+      return U2FRegisterPost.default(vars)(req as any, res as any)
+        .then(function () {
+          assert.equal("user", mocks.userDataStore.saveU2FRegistrationStub.getCall(0).args[0]);
+          assert.equal(authSession.identity_check, undefined);
+        });
+    });
+
+    it("should return error message on finishRegistration error", function () {
+      mocks.u2f.checkRegistrationStub.returns({ errorCode: 500 });
+
+      authSession.register_request = {
+        appId: "app",
+        challenge: "challenge",
+        keyHandle: "key",
+        version: "U2F_V2"
+      };
+
+      return U2FRegisterPost.default(vars)(req as any, res as any)
+        .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
+        .catch(function () {
+          assert.equal(200, res.status.getCall(0).args[0]);
+          assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "Operation failed."
+          });
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should return error message when register_request is not provided", function () {
+      mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve());
+      authSession.register_request = undefined;
+      return U2FRegisterPost.default(vars)(req as any, res as any)
+        .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
+        .catch(function () {
+          assert.equal(200, res.status.getCall(0).args[0]);
+          assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "Operation failed."
+          });
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should return error message when no auth request has been initiated", function () {
+      mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve());
+      authSession.register_request = undefined;
+      return U2FRegisterPost.default(vars)(req as any, res as any)
+        .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
+        .catch(function () {
+          assert.equal(200, res.status.getCall(0).args[0]);
+          assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "Operation failed."
+          });
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should return error message when identity has not been verified", function () {
+      authSession.identity_check = undefined;
+      return U2FRegisterPost.default(vars)(req as any, res as any)
+        .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
+        .catch(function () {
+          assert.equal(200, res.status.getCall(0).args[0]);
+          assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "Operation failed."
+          });
+          return BluebirdPromise.resolve();
+        });
+    });
+  }
+});
+
diff --git a/themes/squares/server/src/lib/routes/secondfactor/u2f/register/post.ts b/themes/squares/server/src/lib/routes/secondfactor/u2f/register/post.ts
new file mode 100644
index 00000000..7296ccbe
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/u2f/register/post.ts
@@ -0,0 +1,64 @@
+
+import { UserDataStore } from "../../../../storage/UserDataStore";
+import objectPath = require("object-path");
+import u2f_common = require("../U2FCommon");
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import U2f = require("u2f");
+import { U2FRegistration } from "../../../../../../types/U2FRegistration";
+import redirect from "../../redirect";
+import ErrorReplies = require("../../../../ErrorReplies");
+import { ServerVariables } from "../../../../ServerVariables";
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+
+
+export default function (vars: ServerVariables) {
+  function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    const appid = u2f_common.extract_app_id(req);
+    const registrationResponse: U2f.RegistrationData = req.body;
+
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      const registrationRequest = authSession.register_request;
+
+      if (!registrationRequest) {
+        return reject(new Error("No registration request"));
+      }
+
+      if (!authSession.identity_check
+        || authSession.identity_check.challenge != "u2f-register") {
+        return reject(new Error("Bad challenge for registration request"));
+      }
+
+      vars.logger.info(req, "Finishing registration");
+      vars.logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
+      vars.logger.debug(req, "RegistrationResponse = %s", JSON.stringify(registrationResponse));
+
+      return resolve(vars.u2f.checkRegistration(registrationRequest, registrationResponse));
+    })
+      .then(function (u2fResult: U2f.RegistrationResult | U2f.Error): BluebirdPromise<void> {
+        if (objectPath.has(u2fResult, "errorCode"))
+          return BluebirdPromise.reject(new Error("Error while registering."));
+
+        const registrationResult: U2f.RegistrationResult = u2fResult as U2f.RegistrationResult;
+        vars.logger.info(req, "Store registration and reply");
+        vars.logger.debug(req, "RegistrationResult = %s", JSON.stringify(registrationResult));
+        const registration: U2FRegistration = {
+          keyHandle: registrationResult.keyHandle,
+          publicKey: registrationResult.publicKey
+        };
+        return vars.userDataStore.saveU2FRegistration(authSession.userid, appid, registration);
+      })
+      .then(function () {
+        authSession.identity_check = undefined;
+        redirect(vars)(req, res);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  }
+  return handler;
+}
diff --git a/themes/squares/server/src/lib/routes/secondfactor/u2f/register_request/get.spec.ts b/themes/squares/server/src/lib/routes/secondfactor/u2f/register_request/get.spec.ts
new file mode 100644
index 00000000..a207c910
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/u2f/register_request/get.spec.ts
@@ -0,0 +1,86 @@
+
+import sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+import U2FRegisterRequestGet = require("./get");
+import ExpressMock = require("../../../../stubs/express.spec");
+import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
+import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../../ServerVariables";
+
+describe("routes/secondfactor/u2f/register_request/get", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    req = ExpressMock.RequestMock();
+    req.originalUrl = "/api/xxxx";
+    req.app = {};
+    req.session = {
+      auth: {
+        userid: "user",
+        first_factor: true,
+        second_factor: false,
+        identity_check: {
+          challenge: "u2f-register",
+          userid: "user"
+        }
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+
+    res = ExpressMock.ResponseMock();
+    res.send = sinon.spy();
+    res.json = sinon.spy();
+    res.status = sinon.spy();
+  });
+
+  describe("test registration request", () => {
+    it("should send back the registration request and save it in the session", function () {
+      const expectedRequest = {
+        test: "abc"
+      };
+      mocks.u2f.requestStub.returns(BluebirdPromise.resolve(expectedRequest));
+      return U2FRegisterRequestGet.default(vars)(req as any, res as any)
+        .then(function () {
+          Assert.deepEqual(expectedRequest, res.json.getCall(0).args[0]);
+        });
+    });
+
+    it("should return internal error on registration request", function () {
+      res.send = sinon.spy();
+      const user_key_container = {};
+      mocks.u2f.requestStub.returns(BluebirdPromise.reject("Internal error"));
+      return U2FRegisterRequestGet.default(vars)(req as any, res as any)
+        .then(function () {
+          Assert.equal(res.status.getCall(0).args[0], 200);
+          Assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "Operation failed."
+          });
+        });
+    });
+
+    it("should return forbidden if identity has not been verified", function () {
+      req.session.auth.identity_check = undefined;
+      return U2FRegisterRequestGet.default(vars)(req as any, res as any)
+        .then(function () {
+          Assert.equal(403, res.status.getCall(0).args[0]);
+        });
+    });
+  });
+});
+
diff --git a/themes/squares/server/src/lib/routes/secondfactor/u2f/register_request/get.ts b/themes/squares/server/src/lib/routes/secondfactor/u2f/register_request/get.ts
new file mode 100644
index 00000000..f611af93
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/u2f/register_request/get.ts
@@ -0,0 +1,43 @@
+
+import { UserDataStore } from "../../../../storage/UserDataStore";
+
+import objectPath = require("object-path");
+import u2f_common = require("../U2FCommon");
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import U2f = require("u2f");
+import ErrorReplies = require("../../../../ErrorReplies");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import { ServerVariables } from "../../../../ServerVariables";
+
+export default function (vars: ServerVariables) {
+  function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    const appid: string = u2f_common.extract_app_id(req);
+
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      if (!authSession.identity_check
+        || authSession.identity_check.challenge != "u2f-register") {
+        res.status(403);
+        res.send();
+        return reject(new Error("Bad challenge."));
+      }
+
+      vars.logger.info(req, "Starting registration for appId '%s'", appid);
+      return resolve(vars.u2f.request(appid));
+    })
+      .then(function (registrationRequest: U2f.Request) {
+        vars.logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
+        authSession.register_request = registrationRequest;
+        res.json(registrationRequest);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  }
+
+  return handler;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/secondfactor/u2f/sign/post.spec.ts b/themes/squares/server/src/lib/routes/secondfactor/u2f/sign/post.spec.ts
new file mode 100644
index 00000000..9b137e66
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/u2f/sign/post.spec.ts
@@ -0,0 +1,101 @@
+
+import sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+import U2FSignPost = require("./post");
+import { ServerVariables } from "../../../../ServerVariables";
+import winston = require("winston");
+
+import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../ServerVariablesMockBuilder.spec";
+import ExpressMock = require("../../../../stubs/express.spec");
+import U2FMock = require("../../../../stubs/u2f.spec");
+import U2f = require("u2f");
+import { Level } from "../../../../authentication/Level";
+
+describe("routes/secondfactor/u2f/sign/post", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    req = ExpressMock.RequestMock();
+    req.app = {};
+    req.originalUrl = "/api/xxxx";
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req.session = {
+      auth: {
+        userid: "user",
+        authentication_level: Level.ONE_FACTOR,
+        identity_check: {
+          challenge: "u2f-register",
+          userid: "user"
+        }
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    res = ExpressMock.ResponseMock();
+    res.send = sinon.spy();
+    res.json = sinon.spy();
+    res.status = sinon.spy();
+  });
+
+  it("should return status code 204", function () {
+    const expectedStatus = {
+      keyHandle: "keyHandle",
+      publicKey: "pbk",
+      certificate: "cert"
+    };
+    mocks.u2f.checkSignatureStub.returns(expectedStatus);
+
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({
+      registration: {
+        publicKey: "PUBKEY"
+      }
+    }));
+
+    req.session.auth.sign_request = {
+      appId: "app",
+      challenge: "challenge",
+      keyHandle: "key",
+      version: "U2F_V2"
+    };
+    return U2FSignPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal(req.session.auth.authentication_level, Level.TWO_FACTOR);
+      });
+  });
+
+  it("should return unauthorized error on registration request internal error", function () {
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({
+      registration: {
+        publicKey: "PUBKEY"
+      }
+    }));
+    mocks.u2f.checkSignatureStub.returns({ errorCode: 500 });
+
+    req.session.auth.sign_request = {
+      appId: "app",
+      challenge: "challenge",
+      keyHandle: "key",
+      version: "U2F_V2"
+    };
+    return U2FSignPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal(res.status.getCall(0).args[0], 200);
+        Assert.deepEqual(res.send.getCall(0).args[0],
+          { error: "Operation failed." });
+      });
+  });
+});
+
diff --git a/themes/squares/server/src/lib/routes/secondfactor/u2f/sign/post.ts b/themes/squares/server/src/lib/routes/secondfactor/u2f/sign/post.ts
new file mode 100644
index 00000000..7ee711c2
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/u2f/sign/post.ts
@@ -0,0 +1,57 @@
+
+import objectPath = require("object-path");
+import u2f_common = require("../U2FCommon");
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import { UserDataStore } from "../../../../storage/UserDataStore";
+import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument";
+import { Winston } from "../../../../../../types/Dependencies";
+import U2f = require("u2f");
+import exceptions = require("../../../../Exceptions");
+import redirect from "../../redirect";
+import ErrorReplies = require("../../../../ErrorReplies");
+import { ServerVariables } from "../../../../ServerVariables";
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+import { Level } from "../../../../authentication/Level";
+
+export default function (vars: ServerVariables) {
+  function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    const appId = u2f_common.extract_app_id(req);
+
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      if (!authSession.sign_request) {
+        const err = new Error("No sign request");
+        ErrorReplies.replyWithError401(req, res, vars.logger)(err);
+        return reject(err);
+      }
+      resolve();
+    })
+      .then(function () {
+        const userid = authSession.userid;
+        return vars.userDataStore.retrieveU2FRegistration(userid, appId);
+      })
+      .then(function (doc: U2FRegistrationDocument): BluebirdPromise<U2f.SignatureResult | U2f.Error> {
+        const signRequest = authSession.sign_request;
+        const signData: U2f.SignatureData = req.body;
+        vars.logger.info(req, "Finish authentication");
+        return BluebirdPromise.resolve(vars.u2f.checkSignature(signRequest, signData, doc.registration.publicKey));
+      })
+      .then(function (result: U2f.SignatureResult | U2f.Error): BluebirdPromise<void> {
+        if (objectPath.has(result, "errorCode"))
+          return BluebirdPromise.reject(new Error("Error while signing"));
+        vars.logger.info(req, "Successful authentication");
+        authSession.authentication_level = Level.TWO_FACTOR;
+        redirect(vars)(req, res);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  }
+
+  return handler;
+}
+
diff --git a/themes/squares/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts b/themes/squares/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts
new file mode 100644
index 00000000..dd52b27e
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts
@@ -0,0 +1,68 @@
+
+import sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import assert = require("assert");
+import U2FSignRequestGet = require("./get");
+import ExpressMock = require("../../../../stubs/express.spec");
+import { Request } from "u2f";
+import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../../ServerVariables";
+
+import { SignMessage } from "../../../../../../../shared/SignMessage";
+
+describe("routes/secondfactor/u2f/sign_request/get", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    req = ExpressMock.RequestMock();
+    req.originalUrl = "/api/xxxx";
+    req.app = {};
+    req.session = {
+      auth: {
+        userid: "user",
+        first_factor: true,
+        second_factor: false,
+        identity_check: {
+          challenge: "u2f-register",
+          userid: "user"
+        }
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    res = ExpressMock.ResponseMock();
+    res.send = sinon.spy();
+    res.json = sinon.spy();
+    res.status = sinon.spy();
+  });
+
+  it("should send back the sign request and save it in the session", function () {
+    const expectedRequest: Request = {
+      version: "U2F_V2",
+      appId: 'app',
+      challenge: 'challenge!'
+    };
+    mocks.u2f.requestStub.returns(expectedRequest);
+    mocks.userDataStore.retrieveU2FRegistrationStub
+      .returns(BluebirdPromise.resolve({
+        registration: {
+          keyHandle: "KeyHandle"
+        }
+      }));
+
+    return U2FSignRequestGet.default(vars)(req as any, res as any)
+      .then(() => {
+        assert.deepEqual(expectedRequest, req.session.auth.sign_request);
+        assert.deepEqual(expectedRequest, res.json.getCall(0).args[0]);
+      });
+  });
+});
+
diff --git a/themes/squares/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts b/themes/squares/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts
new file mode 100644
index 00000000..9e93dde0
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts
@@ -0,0 +1,42 @@
+
+import u2f_common = require("../../../secondfactor/u2f/U2FCommon");
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument";
+import exceptions = require("../../../../Exceptions");
+import ErrorReplies = require("../../../../ErrorReplies");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import { ServerVariables } from "../../../../ServerVariables";
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+
+export default function (vars: ServerVariables) {
+  function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    const appId = u2f_common.extract_app_id(req);
+
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      resolve();
+    })
+      .then(function () {
+        return vars.userDataStore.retrieveU2FRegistration(authSession.userid, appId);
+      })
+      .then(function (doc: U2FRegistrationDocument): BluebirdPromise<void> {
+        if (!doc)
+          return BluebirdPromise.reject(new exceptions.AccessDeniedError("No U2F registration document found."));
+
+        const appId: string = u2f_common.extract_app_id(req);
+        vars.logger.info(req, "Start authentication of app '%s'", appId);
+        vars.logger.debug(req, "AppId = %s, keyHandle = %s", appId, JSON.stringify(doc.registration.keyHandle));
+
+        const request = vars.u2f.request(appId, doc.registration.keyHandle);
+        authSession.sign_request = request;
+        res.json(request);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  }
+  return handler;
+}
diff --git a/themes/squares/server/src/lib/routes/verify/access_control.ts b/themes/squares/server/src/lib/routes/verify/access_control.ts
new file mode 100644
index 00000000..136239ae
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/verify/access_control.ts
@@ -0,0 +1,51 @@
+import Express = require("express");
+import BluebirdPromise = require("bluebird");
+import Util = require("util");
+
+import Exceptions = require("../../Exceptions");
+
+import { Level as AuthorizationLevel } from "../../authorization/Level";
+import { Level as AuthenticationLevel } from "../../authentication/Level";
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import { ServerVariables } from "../../ServerVariables";
+
+function isAuthorized(
+  authorization: AuthorizationLevel,
+  authentication: AuthenticationLevel): boolean {
+
+  if (authorization == AuthorizationLevel.BYPASS) {
+    return true;
+  } else if (authorization == AuthorizationLevel.ONE_FACTOR &&
+    authentication >= AuthenticationLevel.ONE_FACTOR) {
+    return true;
+  } else if (authorization == AuthorizationLevel.TWO_FACTOR &&
+    authentication >= AuthenticationLevel.TWO_FACTOR) {
+    return true;
+  }
+  return false;
+}
+
+export default function (
+  req: Express.Request,
+  vars: ServerVariables,
+  domain: string, resource: string,
+  user: string, groups: string[],
+  authenticationLevel: AuthenticationLevel) {
+
+  return new BluebirdPromise(function (resolve, reject) {
+    const authorizationLevel = vars.authorizer
+      .authorization({domain, resource}, {user, groups});
+
+    if (!isAuthorized(authorizationLevel, authenticationLevel)) {
+      if (authorizationLevel == AuthorizationLevel.DENY) {
+        reject(new Exceptions.NotAuthorizedError(
+          Util.format("User %s is not authorized to access %s%s", user, domain, resource)));
+        return;
+      }
+      reject(new Exceptions.NotAuthenticatedError(Util.format(
+        "User '%s' is not sufficiently authorized to access %s%s.", user, domain, resource)));
+      return;
+    }
+    resolve();
+  });
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/verify/get.spec.ts b/themes/squares/server/src/lib/routes/verify/get.spec.ts
new file mode 100644
index 00000000..67cf19fb
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/verify/get.spec.ts
@@ -0,0 +1,320 @@
+
+import Assert = require("assert");
+import BluebirdPromise = require("bluebird");
+import Express = require("express");
+import Sinon = require("sinon");
+import winston = require("winston");
+
+import VerifyGet = require("./get");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../types/AuthenticationSession";
+import ExpressMock = require("../../stubs/express.spec");
+import { ServerVariables } from "../../ServerVariables";
+import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../ServerVariablesMockBuilder.spec";
+import { Level } from "../../authentication/Level";
+import { Level as AuthorizationLevel } from "../../authorization/Level";
+
+describe("routes/verify/get", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+  let authSession: AuthenticationSession;
+
+  beforeEach(function () {
+    req = ExpressMock.RequestMock();
+    res = ExpressMock.ResponseMock();
+    req.originalUrl = "/api/xxxx";
+    req.query = {
+      redirect: "undefined"
+    };
+    AuthenticationSessionHandler.reset(req as any);
+    req.headers["x-original-url"] = "https://secret.example.com/";
+    const s = ServerVariablesMockBuilder.build(false);
+    mocks = s.mocks;
+    vars = s.variables;
+    authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
+  });
+
+  describe("with session cookie", function () {
+    it("should be already authenticated", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      authSession.authentication_level = Level.TWO_FACTOR;
+      authSession.userid = "myuser";
+      authSession.groups = ["mygroup", "othergroup"];
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "myuser");
+          Sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup");
+          Assert.equal(204, res.status.getCall(0).args[0]);
+        });
+    });
+
+    function test_session(_authSession: AuthenticationSession, status_code: number) {
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert.equal(status_code, res.status.getCall(0).args[0]);
+        });
+    }
+
+    function test_non_authenticated_401(authSession: AuthenticationSession) {
+      return test_session(authSession, 401);
+    }
+
+    function test_unauthorized_403(authSession: AuthenticationSession) {
+      return test_session(authSession, 403);
+    }
+
+    function test_authorized(authSession: AuthenticationSession) {
+      return test_session(authSession, 204);
+    }
+
+    describe("given user tries to access a 2-factor endpoint", function () {
+      before(function () {
+        mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      });
+
+      describe("given different cases of session", function () {
+        it("should not be authenticated when second factor is missing", function () {
+          return test_non_authenticated_401({
+            keep_me_logged_in: false,
+            userid: "user",
+            authentication_level: Level.ONE_FACTOR,
+            email: undefined,
+            groups: [],
+            last_activity_datetime: new Date().getTime()
+          });
+        });
+
+        it("should not be authenticated when userid is missing", function () {
+          return test_non_authenticated_401({
+            keep_me_logged_in: false,
+            userid: undefined,
+            authentication_level: Level.TWO_FACTOR,
+            email: undefined,
+            groups: [],
+            last_activity_datetime: new Date().getTime()
+          });
+        });
+
+        it("should not be authenticated when level is insufficient", function () {
+          return test_non_authenticated_401({
+            keep_me_logged_in: false,
+            userid: "user",
+            authentication_level: Level.NOT_AUTHENTICATED,
+            email: undefined,
+            groups: [],
+            last_activity_datetime: new Date().getTime()
+          });
+        });
+
+        it("should not be authenticated when session has not be initiated", function () {
+          return test_non_authenticated_401(undefined);
+        });
+
+        it("should not be authenticated when domain is not allowed for user", function () {
+          authSession.authentication_level = Level.TWO_FACTOR;
+          authSession.userid = "myuser";
+          req.headers["x-original-url"] = "https://test.example.com/";
+          mocks.authorizer.authorizationMock.returns(AuthorizationLevel.DENY);
+
+          return test_unauthorized_403({
+            keep_me_logged_in: false,
+            authentication_level: Level.TWO_FACTOR,
+            userid: "user",
+            groups: ["group1", "group2"],
+            email: undefined,
+            last_activity_datetime: new Date().getTime()
+          });
+        });
+      });
+    });
+
+    describe("given user tries to access a single factor endpoint", function () {
+      beforeEach(function () {
+        req.headers["x-original-url"] = "https://redirect.url/";
+      });
+
+      it("should be authenticated when first factor is validated", function () {
+        mocks.authorizer.authorizationMock.returns(AuthorizationLevel.ONE_FACTOR);
+        authSession.authentication_level = Level.ONE_FACTOR;
+        authSession.userid = "user1";
+        return VerifyGet.default(vars)(req as Express.Request, res as any)
+          .then(function () {
+            Assert(res.status.calledWith(204));
+            Assert(res.send.calledOnce);
+          });
+      });
+
+      it("should be rejected with 401 when not authenticated", function () {
+        mocks.authorizer.authorizationMock.returns(AuthorizationLevel.ONE_FACTOR);
+        authSession.authentication_level = Level.NOT_AUTHENTICATED;
+        return VerifyGet.default(vars)(req as Express.Request, res as any)
+          .then(function () {
+            Assert(res.status.calledWith(401));
+          });
+      });
+    });
+
+    describe("inactivity period", function () {
+      it("should update last inactivity period on requests on /api/verify", function () {
+        mocks.config.session.inactivity = 200000;
+        mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+        const currentTime = new Date().getTime() - 1000;
+        AuthenticationSessionHandler.reset(req as any);
+        authSession.authentication_level = Level.TWO_FACTOR;
+        authSession.userid = "myuser";
+        authSession.groups = ["mygroup", "othergroup"];
+        authSession.last_activity_datetime = currentTime;
+        return VerifyGet.default(vars)(req as Express.Request, res as any)
+          .then(function () {
+            return AuthenticationSessionHandler.get(req as any, vars.logger);
+          })
+          .then(function (authSession) {
+            Assert(authSession.last_activity_datetime > currentTime);
+          });
+      });
+
+      it("should reset session when max inactivity period has been reached", function () {
+        mocks.config.session.inactivity = 1;
+        mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+        const currentTime = new Date().getTime() - 1000;
+        AuthenticationSessionHandler.reset(req as any);
+        authSession.authentication_level = Level.TWO_FACTOR;
+        authSession.userid = "myuser";
+        authSession.groups = ["mygroup", "othergroup"];
+        authSession.last_activity_datetime = currentTime;
+        return VerifyGet.default(vars)(req as Express.Request, res as any)
+          .then(function () {
+            return AuthenticationSessionHandler.get(req as any, vars.logger);
+          })
+          .then(function (authSession) {
+            Assert.equal(authSession.authentication_level, Level.NOT_AUTHENTICATED);
+            Assert.equal(authSession.userid, undefined);
+          });
+      });
+    });
+  });
+
+  describe("response type 401 | 302", function() {
+    it("should return error code 401", function() {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.rejects(new Error(
+        "Invalid credentials"));
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+
+    it("should redirect to provided redirection url", function() {
+      const REDIRECT_URL = "http://redirection_url.com";
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.rejects(new Error(
+        "Invalid credentials"));
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+      req.query["rd"] = REDIRECT_URL;
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.redirect.calledWithExactly(REDIRECT_URL));
+        });
+    });
+  });
+
+  describe("with basic auth", function () {
+    it("should authenticate correctly", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.ONE_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.returns({
+        groups: ["mygroup", "othergroup"],
+      });
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "john");
+          Sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup");
+          Assert.equal(204, res.status.getCall(0).args[0]);
+        });
+    });
+
+    it("should fail when endpoint is protected by two factors", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.config.access_control.rules = [{
+        domain: "secret.example.com",
+        policy: "two_factor"
+      }];
+      mocks.usersDatabase.checkUserPasswordStub.resolves({
+        groups: ["mygroup", "othergroup"],
+      });
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+
+    it("should fail when base64 token is not valid", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.resolves({
+        groups: ["mygroup", "othergroup"],
+      });
+      req.headers["proxy-authorization"] = "Basic i_m*not_a_base64*token";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+
+    it("should fail when base64 token has not format user:psswd", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.resolves({
+        groups: ["mygroup", "othergroup"],
+      });
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzOmJhZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+
+    it("should fail when bad user password is provided", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.rejects(new Error(
+        "Invalid credentials"));
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+
+    it("should fail when resource is restricted", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.resolves({
+        groups: ["mygroup", "othergroup"],
+      });
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+  });
+});
+
diff --git a/themes/squares/server/src/lib/routes/verify/get.ts b/themes/squares/server/src/lib/routes/verify/get.ts
new file mode 100644
index 00000000..f7386169
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/verify/get.ts
@@ -0,0 +1,91 @@
+import BluebirdPromise = require("bluebird");
+import Express = require("express");
+import Exceptions = require("../../Exceptions");
+import ErrorReplies = require("../../ErrorReplies");
+import { ServerVariables } from "../../ServerVariables";
+import GetWithSessionCookieMethod from "./get_session_cookie";
+import GetWithBasicAuthMethod from "./get_basic_auth";
+import Constants = require("../../../../../shared/constants");
+import ObjectPath = require("object-path");
+
+import { AuthenticationSessionHandler }
+  from "../../AuthenticationSessionHandler";
+import { AuthenticationSession }
+  from "../../../../types/AuthenticationSession";
+
+const REMOTE_USER = "Remote-User";
+const REMOTE_GROUPS = "Remote-Groups";
+
+
+function verifyWithSelectedMethod(req: Express.Request, res: Express.Response,
+  vars: ServerVariables, authSession: AuthenticationSession)
+  : () => BluebirdPromise<{ username: string, groups: string[] }> {
+  return function () {
+    const authorization: string = "" + req.headers["proxy-authorization"];
+    if (authorization && authorization.startsWith("Basic "))
+      return GetWithBasicAuthMethod(req, res, vars, authorization);
+
+    return GetWithSessionCookieMethod(req, res, vars, authSession);
+  };
+}
+
+function setRedirectHeader(req: Express.Request, res: Express.Response) {
+  return function () {
+    const originalUrl = ObjectPath.get<Express.Request, string>(
+      req, "headers.x-original-url");
+    res.set("Redirect", originalUrl);
+    return BluebirdPromise.resolve();
+  };
+}
+
+function setUserAndGroupsHeaders(res: Express.Response) {
+  return function (u: { username: string, groups: string[] }) {
+    res.setHeader(REMOTE_USER, u.username);
+    res.setHeader(REMOTE_GROUPS, u.groups.join(","));
+    return BluebirdPromise.resolve();
+  };
+}
+
+function replyWith200(res: Express.Response) {
+  return function () {
+    res.status(204);
+    res.send();
+  };
+}
+
+function getRedirectParam(req: Express.Request) {
+  return req.query[Constants.REDIRECT_QUERY_PARAM] != "undefined"
+    ? req.query[Constants.REDIRECT_QUERY_PARAM]
+    : undefined;
+}
+
+export default function (vars: ServerVariables) {
+  return function (req: Express.Request, res: Express.Response)
+    : BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      resolve();
+    })
+      .then(setRedirectHeader(req, res))
+      .then(verifyWithSelectedMethod(req, res, vars, authSession))
+      .then(setUserAndGroupsHeaders(res))
+      .then(replyWith200(res))
+      // The user is authenticated but has restricted access -> 403
+      .catch(Exceptions.NotAuthorizedError,
+        ErrorReplies.replyWithError403(req, res, vars.logger))
+      .catch(Exceptions.NotAuthenticatedError,
+        ErrorReplies.replyWithError401(req, res, vars.logger))
+      // The user is not yet authenticated -> 401
+      .catch((err) => {
+        const redirectUrl = getRedirectParam(req);
+        if (redirectUrl) {
+          ErrorReplies.redirectTo(redirectUrl, req, res, vars.logger)(err);
+        }
+        else {
+          ErrorReplies.replyWithError401(req, res, vars.logger)(err);
+        }
+      });
+  };
+}
+
diff --git a/themes/squares/server/src/lib/routes/verify/get_basic_auth.ts b/themes/squares/server/src/lib/routes/verify/get_basic_auth.ts
new file mode 100644
index 00000000..af23c76c
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/verify/get_basic_auth.ts
@@ -0,0 +1,55 @@
+import Express = require("express");
+import BluebirdPromise = require("bluebird");
+import ObjectPath = require("object-path");
+import { ServerVariables } from "../../ServerVariables";
+import { AuthenticationSession }
+  from "../../../../types/AuthenticationSession";
+import AccessControl from "./access_control";
+import { URLDecomposer } from "../../utils/URLDecomposer";
+import { Level } from "../../authentication/Level";
+
+export default function (req: Express.Request, res: Express.Response,
+  vars: ServerVariables, authorizationHeader: string)
+  : BluebirdPromise<{ username: string, groups: string[] }> {
+  let username: string;
+  const uri = ObjectPath.get<Express.Request, string>(req, "headers.x-original-url");
+  const urlDecomposition = URLDecomposer.fromUrl(uri);
+
+  return BluebirdPromise.resolve()
+    .then(() => {
+      const base64Re = new RegExp("^Basic ((?:[A-Za-z0-9+/]{4})*" +
+        "(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)$");
+      const isTokenValidBase64 = base64Re.test(authorizationHeader);
+
+      if (!isTokenValidBase64) {
+        return BluebirdPromise.reject(new Error("No valid base64 token found in the header"));
+      }
+
+      const tokenMatches = authorizationHeader.match(base64Re);
+      const base64Token = tokenMatches[1];
+      const decodedToken = Buffer.from(base64Token, "base64").toString();
+      const splittedToken = decodedToken.split(":");
+
+      if (splittedToken.length != 2) {
+        return BluebirdPromise.reject(new Error(
+          "The authorization token is invalid. Expecting 'userid:password'"));
+      }
+
+      username = splittedToken[0];
+      const password = splittedToken[1];
+      return vars.usersDatabase.checkUserPassword(username, password);
+    })
+    .then(function (groupsAndEmails) {
+      return AccessControl(req, vars, urlDecomposition.domain, urlDecomposition.path,
+        username, groupsAndEmails.groups, Level.ONE_FACTOR)
+        .then(() => BluebirdPromise.resolve({
+          username: username,
+          groups: groupsAndEmails.groups
+        }));
+    })
+    .catch(function (err: Error) {
+      return BluebirdPromise.reject(
+        new Error("Unable to authenticate the user with basic auth. Cause: "
+          + err.message));
+    });
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/routes/verify/get_session_cookie.ts b/themes/squares/server/src/lib/routes/verify/get_session_cookie.ts
new file mode 100644
index 00000000..07034481
--- /dev/null
+++ b/themes/squares/server/src/lib/routes/verify/get_session_cookie.ts
@@ -0,0 +1,78 @@
+import Express = require("express");
+import BluebirdPromise = require("bluebird");
+import Util = require("util");
+import ObjectPath = require("object-path");
+
+import Exceptions = require("../../Exceptions");
+import { Configuration } from "../../configuration/schema/Configuration";
+import { ServerVariables } from "../../ServerVariables";
+import { IRequestLogger } from "../../logging/IRequestLogger";
+import { AuthenticationSession }
+  from "../../../../types/AuthenticationSession";
+import { AuthenticationSessionHandler }
+  from "../../AuthenticationSessionHandler";
+import AccessControl from "./access_control";
+import { URLDecomposer } from "../../utils/URLDecomposer";
+
+function verify_inactivity(req: Express.Request,
+  authSession: AuthenticationSession,
+  configuration: Configuration, logger: IRequestLogger)
+  : BluebirdPromise<void> {
+
+  // If inactivity is not specified, then inactivity timeout does not apply
+  if (!configuration.session.inactivity || authSession.keep_me_logged_in) {
+    return BluebirdPromise.resolve();
+  }
+
+  const lastActivityTime = authSession.last_activity_datetime;
+  const currentTime = new Date().getTime();
+  authSession.last_activity_datetime = currentTime;
+
+  const inactivityPeriodMs = currentTime - lastActivityTime;
+  logger.debug(req, "Inactivity period was %s s and max period was %s.",
+    inactivityPeriodMs / 1000, configuration.session.inactivity / 1000);
+  if (inactivityPeriodMs < configuration.session.inactivity) {
+    return BluebirdPromise.resolve();
+  }
+
+  logger.debug(req, "Session has been reset after too long inactivity period.");
+  AuthenticationSessionHandler.reset(req);
+  return BluebirdPromise.reject(new Error("Inactivity period exceeded."));
+}
+
+export default function (req: Express.Request, res: Express.Response,
+  vars: ServerVariables, authSession: AuthenticationSession)
+  : BluebirdPromise<{ username: string, groups: string[] }> {
+
+  return BluebirdPromise.resolve()
+    .then(() => {
+    const username = authSession.userid;
+    const groups = authSession.groups;
+
+    if (!authSession.userid) {
+      return BluebirdPromise.reject(new Exceptions.AccessDeniedError(
+        "userid is missing"));
+    }
+
+    const originalUrl = ObjectPath.get<Express.Request, string>(
+      req, "headers.x-original-url");
+    const originalUri =
+      ObjectPath.get<Express.Request, string>(req, "headers.x-original-uri");
+
+    const d = URLDecomposer.fromUrl(originalUrl);
+    vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", d.domain,
+      d.path, username, groups.join(","));
+    return AccessControl(req, vars, d.domain, d.path, username, groups,
+      authSession.authentication_level);
+  })
+    .then(() => {
+      return verify_inactivity(req, authSession,
+        vars.config, vars.logger);
+    })
+    .then(() => {
+      return BluebirdPromise.resolve({
+        username: authSession.userid,
+        groups: authSession.groups
+      });
+    });
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/AuthenticationTraceDocument.d.ts b/themes/squares/server/src/lib/storage/AuthenticationTraceDocument.d.ts
new file mode 100644
index 00000000..69818c05
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/AuthenticationTraceDocument.d.ts
@@ -0,0 +1,6 @@
+
+export interface AuthenticationTraceDocument {
+    userId: string;
+    date: Date;
+    isAuthenticationSuccessful: boolean;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/CollectionFactoryFactory.ts b/themes/squares/server/src/lib/storage/CollectionFactoryFactory.ts
new file mode 100644
index 00000000..92b29abf
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/CollectionFactoryFactory.ts
@@ -0,0 +1,15 @@
+import { ICollectionFactory } from "./ICollectionFactory";
+import { NedbCollectionFactory } from "./nedb/NedbCollectionFactory";
+import { MongoCollectionFactory } from "./mongo/MongoCollectionFactory";
+import { IMongoClient } from "../connectors/mongo/IMongoClient";
+
+
+export class CollectionFactoryFactory {
+  static createNedb(options: Nedb.DataStoreOptions): ICollectionFactory {
+    return new NedbCollectionFactory(options);
+  }
+
+  static createMongo(client: IMongoClient): ICollectionFactory {
+    return new MongoCollectionFactory(client);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/CollectionFactoryStub.spec.ts b/themes/squares/server/src/lib/storage/CollectionFactoryStub.spec.ts
new file mode 100644
index 00000000..17f8bb02
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/CollectionFactoryStub.spec.ts
@@ -0,0 +1,16 @@
+import BluebirdPromise = require("bluebird");
+import Sinon = require("sinon");
+import { ICollection } from "./ICollection";
+import { ICollectionFactory } from "./ICollectionFactory";
+
+export class CollectionFactoryStub implements ICollectionFactory {
+    buildStub: Sinon.SinonStub;
+
+    constructor() {
+        this.buildStub = Sinon.stub();
+    }
+
+    build(collectionName: string): ICollection {
+        return this.buildStub(collectionName);
+    }
+}
diff --git a/themes/squares/server/src/lib/storage/CollectionStub.spec.ts b/themes/squares/server/src/lib/storage/CollectionStub.spec.ts
new file mode 100644
index 00000000..42895d67
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/CollectionStub.spec.ts
@@ -0,0 +1,39 @@
+import BluebirdPromise = require("bluebird");
+import Sinon = require("sinon");
+import { ICollection } from "./ICollection";
+
+export class CollectionStub implements ICollection {
+    findStub: Sinon.SinonStub;
+    findOneStub: Sinon.SinonStub;
+    updateStub: Sinon.SinonStub;
+    removeStub: Sinon.SinonStub;
+    insertStub: Sinon.SinonStub;
+
+    constructor() {
+        this.findStub = Sinon.stub();
+        this.findOneStub = Sinon.stub();
+        this.updateStub = Sinon.stub();
+        this.removeStub = Sinon.stub();
+        this.insertStub = Sinon.stub();
+    }
+
+    find(filter: any, sortKeys: any, count: number): BluebirdPromise<any> {
+        return this.findStub(filter, sortKeys, count);
+    }
+
+    findOne(filter: any): BluebirdPromise<any> {
+        return this.findOneStub(filter);
+    }
+
+    update(filter: any, document: any, options: any): BluebirdPromise<any> {
+        return this.updateStub(filter, document, options);
+    }
+
+    remove(filter: any): BluebirdPromise<any> {
+        return this.removeStub(filter);
+    }
+
+    insert(document: any): BluebirdPromise<any> {
+        return this.insertStub(document);
+    }
+}
diff --git a/themes/squares/server/src/lib/storage/ICollection.d.ts b/themes/squares/server/src/lib/storage/ICollection.d.ts
new file mode 100644
index 00000000..caa6c2a8
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/ICollection.d.ts
@@ -0,0 +1,11 @@
+/* istanbul ignore next */
+import BluebirdPromise = require("bluebird");
+
+/* istanbul ignore next */
+export interface ICollection {
+    find(query: any, sortKeys: any, count: number): BluebirdPromise<any>;
+    findOne(query: any): BluebirdPromise<any>;
+    update(query: any, updateQuery: any, options?: any): BluebirdPromise<any>;
+    remove(query: any): BluebirdPromise<any>;
+    insert(document: any): BluebirdPromise<any>;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/ICollectionFactory.d.ts b/themes/squares/server/src/lib/storage/ICollectionFactory.d.ts
new file mode 100644
index 00000000..39eb42c7
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/ICollectionFactory.d.ts
@@ -0,0 +1,6 @@
+
+import { ICollection } from "./ICollection";
+
+export interface ICollectionFactory {
+    build(collectionName: string): ICollection;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/IUserDataStore.d.ts b/themes/squares/server/src/lib/storage/IUserDataStore.d.ts
new file mode 100644
index 00000000..81df482a
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/IUserDataStore.d.ts
@@ -0,0 +1,21 @@
+import BluebirdPromise = require("bluebird");
+import { TOTPSecretDocument } from "./TOTPSecretDocument";
+import { U2FRegistrationDocument } from "./U2FRegistrationDocument";
+import { U2FRegistration } from "../../../types/U2FRegistration";
+import { TOTPSecret } from "../../../types/TOTPSecret";
+import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
+import { IdentityValidationDocument } from "./IdentityValidationDocument";
+
+export interface IUserDataStore {
+    saveU2FRegistration(userId: string, appId: string, registration: U2FRegistration): BluebirdPromise<void>;
+    retrieveU2FRegistration(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument>;
+
+    saveAuthenticationTrace(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void>;
+    retrieveLatestAuthenticationTraces(userId: string, count: number): BluebirdPromise<AuthenticationTraceDocument[]>;
+
+    produceIdentityValidationToken(userId: string, token: string, challenge: string, maxAge: number): BluebirdPromise<any>;
+    consumeIdentityValidationToken(token: string, challenge: string): BluebirdPromise<IdentityValidationDocument>;
+
+    saveTOTPSecret(userId: string, secret: TOTPSecret): BluebirdPromise<void>;
+    retrieveTOTPSecret(userId: string): BluebirdPromise<TOTPSecretDocument>;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/IdentityValidationDocument.d.ts b/themes/squares/server/src/lib/storage/IdentityValidationDocument.d.ts
new file mode 100644
index 00000000..e7fd7b3f
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/IdentityValidationDocument.d.ts
@@ -0,0 +1,7 @@
+
+export interface IdentityValidationDocument {
+    userId: string;
+    token: string;
+    challenge: string;
+    maxDate: Date;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/TOTPSecretDocument.d.ts b/themes/squares/server/src/lib/storage/TOTPSecretDocument.d.ts
new file mode 100644
index 00000000..a6c0bf9e
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/TOTPSecretDocument.d.ts
@@ -0,0 +1,6 @@
+import { TOTPSecret } from "../../../types/TOTPSecret";
+
+export interface TOTPSecretDocument {
+  userid: string;
+  secret: TOTPSecret;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/U2FRegistrationDocument.d.ts b/themes/squares/server/src/lib/storage/U2FRegistrationDocument.d.ts
new file mode 100644
index 00000000..efec6cb1
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/U2FRegistrationDocument.d.ts
@@ -0,0 +1,8 @@
+
+import { U2FRegistration } from "../../../types/U2FRegistration";
+
+export interface U2FRegistrationDocument {
+  userId: string;
+  appId: string;
+  registration: U2FRegistration;
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/UserDataStore.spec.ts b/themes/squares/server/src/lib/storage/UserDataStore.spec.ts
new file mode 100644
index 00000000..66fb8546
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/UserDataStore.spec.ts
@@ -0,0 +1,264 @@
+
+import * as Assert from "assert";
+import * as Sinon from "sinon";
+import * as MockDate from "mockdate";
+import BluebirdPromise = require("bluebird");
+
+import { UserDataStore } from "./UserDataStore";
+import { TOTPSecret } from "../../../types/TOTPSecret";
+import { U2FRegistration } from "../../../types/U2FRegistration";
+import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
+import { CollectionStub } from "./CollectionStub.spec";
+import { CollectionFactoryStub } from "./CollectionFactoryStub.spec";
+
+describe("storage/UserDataStore", function () {
+  let factory: CollectionFactoryStub;
+  let collection: CollectionStub;
+  let userId: string;
+  let appId: string;
+  let totpSecret: TOTPSecret;
+  let u2fRegistration: U2FRegistration;
+
+  beforeEach(function () {
+    factory = new CollectionFactoryStub();
+    collection = new CollectionStub();
+
+    userId = "user";
+    appId = "https://myappId";
+
+    totpSecret = {
+      ascii: "abc",
+      base32: "ABCDKZLEFZGREJK",
+      otpauth_url: "totp://test",
+      google_auth_qr: "dummy",
+      hex: "dummy",
+      qr_code_ascii: "dummy",
+      qr_code_base32: "dummy",
+      qr_code_hex: "dummy"
+    };
+
+    u2fRegistration = {
+      keyHandle: "KEY_HANDLE",
+      publicKey: "publickey"
+    };
+  });
+
+  it("should correctly creates collections", function () {
+    new UserDataStore(factory);
+
+    Assert.equal(4, factory.buildStub.callCount);
+    Assert(factory.buildStub.calledWith("authentication_traces"));
+    Assert(factory.buildStub.calledWith("identity_validation_tokens"));
+    Assert(factory.buildStub.calledWith("u2f_registrations"));
+    Assert(factory.buildStub.calledWith("totp_secrets"));
+  });
+
+  describe("TOTP secrets collection", function () {
+    it("should save a totp secret", function () {
+      factory.buildStub.returns(collection);
+      collection.updateStub.returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.saveTOTPSecret(userId, totpSecret)
+        .then(function (doc) {
+          Assert(collection.updateStub.calledOnce);
+          Assert(collection.updateStub.calledWith({ userId: userId }, {
+            userId: userId,
+            secret: totpSecret
+          }, { upsert: true }));
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should retrieve a totp secret", function () {
+      factory.buildStub.returns(collection);
+      collection.findOneStub.withArgs().returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.retrieveTOTPSecret(userId)
+        .then(function (doc) {
+          Assert(collection.findOneStub.calledOnce);
+          Assert(collection.findOneStub.calledWith({ userId: userId }));
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+
+  describe("U2F secrets collection", function () {
+    it("should save a U2F secret", function () {
+      factory.buildStub.returns(collection);
+      collection.updateStub.returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.saveU2FRegistration(userId, appId, u2fRegistration)
+        .then(function (doc) {
+          Assert(collection.updateStub.calledOnce);
+          Assert(collection.updateStub.calledWith({
+            userId: userId,
+            appId: appId
+          }, {
+              userId: userId,
+              appId: appId,
+              registration: u2fRegistration
+            }, { upsert: true }));
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should retrieve a U2F secret", function () {
+      factory.buildStub.returns(collection);
+      collection.findOneStub.withArgs().returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.retrieveU2FRegistration(userId, appId)
+        .then(function (doc) {
+          Assert(collection.findOneStub.calledOnce);
+          Assert(collection.findOneStub.calledWith({
+            userId: userId,
+            appId: appId
+          }));
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+
+
+  describe("Regulator traces collection", function () {
+    it("should save a trace", function () {
+      factory.buildStub.returns(collection);
+      collection.insertStub.returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.saveAuthenticationTrace(userId, true)
+        .then(function (doc) {
+          Assert(collection.insertStub.calledOnce);
+          Assert(collection.insertStub.calledWith({
+            userId: userId,
+            date: Sinon.match.date,
+            isAuthenticationSuccessful: true
+          }));
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    function should_retrieve_latest_authentication_traces(count: number) {
+      factory.buildStub.returns(collection);
+      collection.findStub.withArgs().returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.retrieveLatestAuthenticationTraces(userId, count)
+        .then(function (doc: AuthenticationTraceDocument[]) {
+          Assert(collection.findStub.calledOnce);
+          Assert(collection.findStub.calledWith({
+            userId: userId,
+          }, { date: -1 }, count));
+          return BluebirdPromise.resolve();
+        });
+    }
+
+    it("should retrieve 3 latest failed authentication traces", function () {
+      should_retrieve_latest_authentication_traces(3);
+    });
+  });
+
+
+  describe("Identity validation collection", function () {
+    it("should save a identity validation token", function () {
+      factory.buildStub.returns(collection);
+      collection.insertStub.returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+      const maxAge = 400;
+      const token = "TOKEN";
+      const challenge = "CHALLENGE";
+
+      return dataStore.produceIdentityValidationToken(userId, token, challenge, maxAge)
+        .then(function (doc) {
+          Assert(collection.insertStub.calledOnce);
+          Assert(collection.insertStub.calledWith({
+            userId: userId,
+            token: token,
+            challenge: challenge,
+            maxDate: Sinon.match.date
+          }));
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should consume an identity token successfully", function () {
+      factory.buildStub.returns(collection);
+
+      MockDate.set(100);
+
+      const token = "TOKEN";
+      const challenge = "CHALLENGE";
+
+      collection.findOneStub.withArgs().returns(BluebirdPromise.resolve({
+        userId: "USER",
+        token: token,
+        challenge: challenge,
+        maxDate: new Date()
+      }));
+      collection.removeStub.returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      MockDate.set(80);
+
+      return dataStore.consumeIdentityValidationToken(token, challenge)
+        .then(function (doc) {
+          MockDate.reset();
+          Assert(collection.findOneStub.calledOnce);
+          Assert(collection.findOneStub.calledWith({
+            token: token,
+            challenge: challenge
+          }));
+
+          Assert(collection.removeStub.calledOnce);
+          Assert(collection.removeStub.calledWith({
+            token: token,
+            challenge: challenge
+          }));
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should consume an expired identity token", function () {
+      factory.buildStub.returns(collection);
+
+      MockDate.set(0);
+
+      const token = "TOKEN";
+      const challenge = "CHALLENGE";
+
+      collection.findOneStub.withArgs().returns(BluebirdPromise.resolve({
+        userId: "USER",
+        token: token,
+        challenge: challenge,
+        maxDate: new Date()
+      }));
+
+      const dataStore = new UserDataStore(factory);
+
+      MockDate.set(80000);
+
+      return dataStore.consumeIdentityValidationToken(token, challenge)
+        .then(function () { return BluebirdPromise.reject(new Error("should not be here")); })
+        .catch(function () {
+          MockDate.reset();
+          Assert(collection.findOneStub.calledOnce);
+          Assert(collection.findOneStub.calledWith({
+            token: token,
+            challenge: challenge
+          }));
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+});
diff --git a/themes/squares/server/src/lib/storage/UserDataStore.ts b/themes/squares/server/src/lib/storage/UserDataStore.ts
new file mode 100644
index 00000000..27b0cddb
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/UserDataStore.ts
@@ -0,0 +1,143 @@
+import * as BluebirdPromise from "bluebird";
+import * as path from "path";
+import { IUserDataStore } from "./IUserDataStore";
+import { ICollection } from "./ICollection";
+import { ICollectionFactory } from "./ICollectionFactory";
+import { TOTPSecretDocument } from "./TOTPSecretDocument";
+import { U2FRegistrationDocument } from "./U2FRegistrationDocument";
+import { U2FRegistration } from "../../../types/U2FRegistration";
+import { TOTPSecret } from "../../../types/TOTPSecret";
+import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
+import { IdentityValidationDocument } from "./IdentityValidationDocument";
+
+// Constants
+
+const IDENTITY_VALIDATION_TOKENS_COLLECTION_NAME = "identity_validation_tokens";
+const AUTHENTICATION_TRACES_COLLECTION_NAME = "authentication_traces";
+
+const U2F_REGISTRATIONS_COLLECTION_NAME = "u2f_registrations";
+const TOTP_SECRETS_COLLECTION_NAME = "totp_secrets";
+
+
+export interface U2FRegistrationKey {
+  userId: string;
+  appId: string;
+}
+
+// Source
+
+export class UserDataStore implements IUserDataStore {
+  private u2fSecretCollection: ICollection;
+  private identityCheckTokensCollection: ICollection;
+  private authenticationTracesCollection: ICollection;
+  private totpSecretCollection: ICollection;
+
+  private collectionFactory: ICollectionFactory;
+
+  constructor(collectionFactory: ICollectionFactory) {
+    this.collectionFactory = collectionFactory;
+
+    this.u2fSecretCollection = this.collectionFactory.build(U2F_REGISTRATIONS_COLLECTION_NAME);
+    this.identityCheckTokensCollection = this.collectionFactory.build(IDENTITY_VALIDATION_TOKENS_COLLECTION_NAME);
+    this.authenticationTracesCollection = this.collectionFactory.build(AUTHENTICATION_TRACES_COLLECTION_NAME);
+    this.totpSecretCollection = this.collectionFactory.build(TOTP_SECRETS_COLLECTION_NAME);
+  }
+
+  saveU2FRegistration(userId: string, appId: string, registration: U2FRegistration): BluebirdPromise<void> {
+    const newDocument: U2FRegistrationDocument = {
+      userId: userId,
+      appId: appId,
+      registration: registration
+    };
+
+    const filter: U2FRegistrationKey = {
+      userId: userId,
+      appId: appId
+    };
+
+    return this.u2fSecretCollection.update(filter, newDocument, { upsert: true });
+  }
+
+  retrieveU2FRegistration(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument> {
+    const filter: U2FRegistrationKey = {
+      userId: userId,
+      appId: appId
+    };
+    return this.u2fSecretCollection.findOne(filter);
+  }
+
+  saveAuthenticationTrace(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
+    const newDocument: AuthenticationTraceDocument = {
+      userId: userId,
+      date: new Date(),
+      isAuthenticationSuccessful: isAuthenticationSuccessful,
+    };
+
+    return this.authenticationTracesCollection.insert(newDocument);
+  }
+
+  retrieveLatestAuthenticationTraces(userId: string, count: number): BluebirdPromise<AuthenticationTraceDocument[]> {
+    const q = {
+      userId: userId
+    };
+
+    return this.authenticationTracesCollection.find(q, { date: -1 }, count);
+  }
+
+  produceIdentityValidationToken(userId: string, token: string, challenge: string, maxAge: number): BluebirdPromise<any> {
+    const newDocument: IdentityValidationDocument = {
+      userId: userId,
+      token: token,
+      challenge: challenge,
+      maxDate: new Date(new Date().getTime() + maxAge)
+    };
+
+    return this.identityCheckTokensCollection.insert(newDocument);
+  }
+
+  consumeIdentityValidationToken(token: string, challenge: string): BluebirdPromise<IdentityValidationDocument> {
+    const that = this;
+    const filter = {
+      token: token,
+      challenge: challenge
+    };
+
+    let identityValidationDocument: IdentityValidationDocument;
+
+    return this.identityCheckTokensCollection.findOne(filter)
+      .then(function (doc: IdentityValidationDocument) {
+        if (!doc) {
+          return BluebirdPromise.reject(new Error("Registration token does not exist"));
+        }
+
+        identityValidationDocument = doc;
+        const current_date = new Date();
+        if (current_date > doc.maxDate)
+          return BluebirdPromise.reject(new Error("Registration token is not valid anymore"));
+
+        return that.identityCheckTokensCollection.remove(filter);
+      })
+      .then(() => {
+        return BluebirdPromise.resolve(identityValidationDocument);
+      });
+  }
+
+  saveTOTPSecret(userId: string, secret: TOTPSecret): BluebirdPromise<void> {
+    const doc = {
+      userId: userId,
+      secret: secret
+    };
+
+    const filter = {
+      userId: userId
+    };
+    return this.totpSecretCollection.update(filter, doc, { upsert: true });
+  }
+
+  retrieveTOTPSecret(userId: string): BluebirdPromise<TOTPSecretDocument> {
+    const filter = {
+      userId: userId
+    };
+    return this.totpSecretCollection.findOne(filter);
+  }
+}
diff --git a/themes/squares/server/src/lib/storage/UserDataStoreStub.spec.ts b/themes/squares/server/src/lib/storage/UserDataStoreStub.spec.ts
new file mode 100644
index 00000000..5ea27a2d
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/UserDataStoreStub.spec.ts
@@ -0,0 +1,64 @@
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+
+import { TOTPSecretDocument } from "./TOTPSecretDocument";
+import { U2FRegistrationDocument } from "./U2FRegistrationDocument";
+import { U2FRegistration } from "../../../types/U2FRegistration";
+import { TOTPSecret } from "../../../types/TOTPSecret";
+import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
+import { IdentityValidationDocument } from "./IdentityValidationDocument";
+import { IUserDataStore } from "./IUserDataStore";
+
+export class UserDataStoreStub implements IUserDataStore {
+    saveU2FRegistrationStub: Sinon.SinonStub;
+    retrieveU2FRegistrationStub: Sinon.SinonStub;
+    saveAuthenticationTraceStub: Sinon.SinonStub;
+    retrieveLatestAuthenticationTracesStub: Sinon.SinonStub;
+    produceIdentityValidationTokenStub: Sinon.SinonStub;
+    consumeIdentityValidationTokenStub: Sinon.SinonStub;
+    saveTOTPSecretStub: Sinon.SinonStub;
+    retrieveTOTPSecretStub: Sinon.SinonStub;
+
+    constructor() {
+        this.saveU2FRegistrationStub = Sinon.stub();
+        this.retrieveU2FRegistrationStub = Sinon.stub();
+        this.saveAuthenticationTraceStub = Sinon.stub();
+        this.retrieveLatestAuthenticationTracesStub = Sinon.stub();
+        this.produceIdentityValidationTokenStub = Sinon.stub();
+        this.consumeIdentityValidationTokenStub = Sinon.stub();
+        this.saveTOTPSecretStub = Sinon.stub();
+        this.retrieveTOTPSecretStub = Sinon.stub();
+    }
+
+    saveU2FRegistration(userId: string, appId: string, registration: U2FRegistration): BluebirdPromise<void> {
+        return this.saveU2FRegistrationStub(userId, appId, registration);
+    }
+
+    retrieveU2FRegistration(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument> {
+        return this.retrieveU2FRegistrationStub(userId, appId);
+    }
+
+    saveAuthenticationTrace(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
+        return this.saveAuthenticationTraceStub(userId, isAuthenticationSuccessful);
+    }
+
+    retrieveLatestAuthenticationTraces(userId: string, count: number): BluebirdPromise<AuthenticationTraceDocument[]> {
+        return this.retrieveLatestAuthenticationTracesStub(userId, count);
+    }
+
+    produceIdentityValidationToken(userId: string, token: string, challenge: string, maxAge: number): BluebirdPromise<any> {
+        return this.produceIdentityValidationTokenStub(userId, token, challenge, maxAge);
+    }
+
+    consumeIdentityValidationToken(token: string, challenge: string): BluebirdPromise<IdentityValidationDocument> {
+        return this.consumeIdentityValidationTokenStub(token, challenge);
+    }
+
+    saveTOTPSecret(userId: string, secret: TOTPSecret): BluebirdPromise<void> {
+        return this.saveTOTPSecretStub(userId, secret);
+    }
+
+    retrieveTOTPSecret(userId: string): BluebirdPromise<TOTPSecretDocument> {
+        return this.retrieveTOTPSecretStub(userId);
+    }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/mongo/MongoCollection.spec.ts b/themes/squares/server/src/lib/storage/mongo/MongoCollection.spec.ts
new file mode 100644
index 00000000..74a773a1
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/mongo/MongoCollection.spec.ts
@@ -0,0 +1,110 @@
+import Assert = require("assert");
+import Sinon = require("sinon");
+import MongoDB = require("mongodb");
+import BluebirdPromise = require("bluebird");
+import { MongoClientStub } from "../../connectors/mongo/MongoClientStub.spec";
+import { MongoCollection } from "./MongoCollection";
+
+describe("storage/mongo/MongoCollection", function () {
+  let mongoCollectionStub: any;
+  let mongoClientStub: MongoClientStub;
+  let findStub: Sinon.SinonStub;
+  let findOneStub: Sinon.SinonStub;
+  let insertOneStub: Sinon.SinonStub;
+  let updateStub: Sinon.SinonStub;
+  let removeStub: Sinon.SinonStub;
+  let countStub: Sinon.SinonStub;
+  const COLLECTION_NAME = "collection";
+
+  before(function () {
+    mongoClientStub = new MongoClientStub();
+    mongoCollectionStub = Sinon.createStubInstance(require("mongodb").Collection as any);
+    findStub = mongoCollectionStub.find as Sinon.SinonStub;
+    findOneStub = mongoCollectionStub.findOne as Sinon.SinonStub;
+    insertOneStub = mongoCollectionStub.insertOne as Sinon.SinonStub;
+    updateStub = mongoCollectionStub.update as Sinon.SinonStub;
+    removeStub = mongoCollectionStub.remove as Sinon.SinonStub;
+    countStub = mongoCollectionStub.count as Sinon.SinonStub;
+    mongoClientStub.collectionStub.returns(
+      BluebirdPromise.resolve(mongoCollectionStub)
+    );
+  });
+
+  describe("find", function () {
+    it("should find a document in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      findStub.returns({
+        sort: Sinon.stub().returns({
+          limit: Sinon.stub().returns({
+            toArray: Sinon.stub().returns(BluebirdPromise.resolve([]))
+          })
+        })
+      });
+
+      return collection.find({ key: "KEY" })
+        .then(function () {
+          Assert(findStub.calledWith({ key: "KEY" }));
+        });
+    });
+  });
+
+  describe("findOne", function () {
+    it("should find one document in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      findOneStub.returns(BluebirdPromise.resolve({}));
+
+      return collection.findOne({ key: "KEY" })
+        .then(function () {
+          Assert(findOneStub.calledWith({ key: "KEY" }));
+        });
+    });
+  });
+
+  describe("insert", function () {
+    it("should insert a document in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      insertOneStub.returns(BluebirdPromise.resolve({}));
+
+      return collection.insert({ key: "KEY" })
+        .then(function () {
+          Assert(insertOneStub.calledWith({ key: "KEY" }));
+        });
+    });
+  });
+
+  describe("update", function () {
+    it("should update a document in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      updateStub.returns(BluebirdPromise.resolve({}));
+
+      return collection.update({ key: "KEY" }, { key: "KEY", value: 1 })
+        .then(function () {
+          Assert(updateStub.calledWith({ key: "KEY" }, { key: "KEY", value: 1 }));
+        });
+    });
+  });
+
+  describe("remove", function () {
+    it("should remove a document in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      removeStub.returns(BluebirdPromise.resolve({}));
+
+      return collection.remove({ key: "KEY" })
+        .then(function () {
+          Assert(removeStub.calledWith({ key: "KEY" }));
+        });
+    });
+  });
+
+  describe("count", function () {
+    it("should count documents in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      countStub.returns(BluebirdPromise.resolve({}));
+
+      return collection.count({ key: "KEY" })
+        .then(function () {
+          Assert(countStub.calledWith({ key: "KEY" }));
+        });
+    });
+  });
+});
diff --git a/themes/squares/server/src/lib/storage/mongo/MongoCollection.ts b/themes/squares/server/src/lib/storage/mongo/MongoCollection.ts
new file mode 100644
index 00000000..9771389f
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/mongo/MongoCollection.ts
@@ -0,0 +1,50 @@
+import Bluebird = require("bluebird");
+import { ICollection } from "../ICollection";
+import MongoDB = require("mongodb");
+import { IMongoClient } from "../../connectors/mongo/IMongoClient";
+
+
+export class MongoCollection implements ICollection {
+  private mongoClient: IMongoClient;
+  private collectionName: string;
+
+  constructor(collectionName: string, mongoClient: IMongoClient) {
+    this.collectionName = collectionName;
+    this.mongoClient = mongoClient;
+  }
+
+  private collection(): Bluebird<MongoDB.Collection> {
+    return this.mongoClient.collection(this.collectionName);
+  }
+
+  find(query: any, sortKeys?: any, count?: number): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.find(query).sort(sortKeys).limit(count))
+      .then((query) => query.toArray());
+  }
+
+  findOne(query: any): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.findOne(query));
+  }
+
+  update(query: any, updateQuery: any, options?: any): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.update(query, updateQuery, options));
+  }
+
+  remove(query: any): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.remove(query));
+  }
+
+  insert(document: any): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.insertOne(document));
+  }
+
+  count(query: any): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.count(query));
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/mongo/MongoCollectionFactory.spec.ts b/themes/squares/server/src/lib/storage/mongo/MongoCollectionFactory.spec.ts
new file mode 100644
index 00000000..bd959cac
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/mongo/MongoCollectionFactory.spec.ts
@@ -0,0 +1,21 @@
+import Assert = require("assert");
+import Sinon = require("sinon");
+import { MongoClientStub } from "../../connectors/mongo/MongoClientStub.spec";
+import { MongoCollectionFactory } from "./MongoCollectionFactory";
+
+describe("storage/mongo/MongoCollectionFactory", function () {
+  let mongoClient: MongoClientStub;
+
+  before(function() {
+    mongoClient = new MongoClientStub();
+  });
+
+  describe("create", function () {
+    it("should create a collection", function () {
+      const COLLECTION_NAME = "COLLECTION_NAME";
+
+      const factory = new MongoCollectionFactory(mongoClient);
+      Assert(factory.build(COLLECTION_NAME));
+    });
+  });
+});
diff --git a/themes/squares/server/src/lib/storage/mongo/MongoCollectionFactory.ts b/themes/squares/server/src/lib/storage/mongo/MongoCollectionFactory.ts
new file mode 100644
index 00000000..14a8262c
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/mongo/MongoCollectionFactory.ts
@@ -0,0 +1,19 @@
+import BluebirdPromise = require("bluebird");
+import { ICollection } from "../ICollection";
+import { ICollectionFactory } from "../ICollectionFactory";
+import { MongoCollection } from "./MongoCollection";
+import path = require("path");
+import MongoDB = require("mongodb");
+import { IMongoClient } from "../../connectors/mongo/IMongoClient";
+
+export class MongoCollectionFactory implements ICollectionFactory {
+  private mongoClient: IMongoClient;
+
+  constructor(mongoClient: IMongoClient) {
+    this.mongoClient = mongoClient;
+  }
+
+  build(collectionName: string): ICollection {
+    return new MongoCollection(collectionName, this.mongoClient);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/nedb/NedbCollection.spec.ts b/themes/squares/server/src/lib/storage/nedb/NedbCollection.spec.ts
new file mode 100644
index 00000000..a69962b6
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/nedb/NedbCollection.spec.ts
@@ -0,0 +1,136 @@
+import Sinon = require("sinon");
+import Assert = require("assert");
+
+import { NedbCollection } from "./NedbCollection";
+
+describe("storage/nedb/NedbCollection", function () {
+  describe("insert", function () {
+    it("should insert one entry", function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      const collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou" });
+
+      return collection.count({}).then(function (count: number) {
+        Assert.equal(1, count);
+      });
+    });
+
+    it("should insert three entries", function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      const collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou" });
+      collection.insert({ key: "hello" });
+      collection.insert({ key: "hey" });
+
+      return collection.count({}).then(function (count: number) {
+        Assert.equal(3, count);
+      });
+    });
+  });
+
+  describe("find", function () {
+    let collection: NedbCollection;
+    before(function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou", value: 1 });
+      collection.insert({ key: "hello" });
+      collection.insert({ key: "hey" });
+      collection.insert({ key: "coucou", value: 2 });
+    });
+
+    it("should find one hello", function () {
+      return collection.find({ key: "hello" }, { key: 1 })
+        .then(function (docs: { key: string }[]) {
+          Assert.equal(1, docs.length);
+          Assert(docs[0].key == "hello");
+        });
+    });
+
+    it("should find two coucou", function () {
+      return collection.find({ key: "coucou" }, { value: 1 })
+        .then(function (docs: { value: number }[]) {
+          Assert.equal(2, docs.length);
+        });
+    });
+  });
+
+  describe("findOne", function () {
+    let collection: NedbCollection;
+    before(function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou", value: 1 });
+      collection.insert({ key: "coucou", value: 1 });
+      collection.insert({ key: "coucou", value: 1 });
+      collection.insert({ key: "coucou", value: 1 });
+    });
+
+    it("should find two coucou", function () {
+      const doc = { key: "coucou", value: 1 };
+      return collection.count(doc)
+        .then(function (count: number) {
+          Assert.equal(4, count);
+          return collection.findOne(doc);
+        });
+    });
+  });
+
+  describe("update", function () {
+    let collection: NedbCollection;
+    before(function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou", value: 1 });
+    });
+
+    it("should update the value", function () {
+      return collection.update({ key: "coucou" }, { key: "coucou", value: 2 }, { multi: true })
+        .then(function () {
+          return collection.find({ key: "coucou" });
+        })
+        .then(function (docs: { key: string, value: number }[]) {
+          Assert.equal(1, docs.length);
+          Assert.equal(2, docs[0].value);
+        });
+    });
+  });
+
+  describe("update", function () {
+    let collection: NedbCollection;
+    before(function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou" });
+      collection.insert({ key: "hello" });
+    });
+
+    it("should update the value", function () {
+      return collection.remove({ key: "coucou" })
+        .then(function () {
+          return collection.count({});
+        })
+        .then(function (count: number) {
+          Assert.equal(1, count);
+        });
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/nedb/NedbCollection.ts b/themes/squares/server/src/lib/storage/nedb/NedbCollection.ts
new file mode 100644
index 00000000..88a93ad0
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/nedb/NedbCollection.ts
@@ -0,0 +1,47 @@
+import BluebirdPromise = require("bluebird");
+import { ICollection } from "../ICollection";
+import Nedb = require("nedb");
+
+declare module "nedb" {
+    export class NedbAsync extends Nedb {
+        constructor(pathOrOptions?: string | Nedb.DataStoreOptions);
+        updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): BluebirdPromise<any>;
+        findOneAsync<T>(query: any): BluebirdPromise<T>;
+        insertAsync<T>(newDoc: T): BluebirdPromise<any>;
+        removeAsync(query: any): BluebirdPromise<any>;
+        countAsync(query: any): BluebirdPromise<number>;
+    }
+}
+
+export class NedbCollection implements ICollection {
+  private collection: Nedb.NedbAsync;
+
+  constructor(options: Nedb.DataStoreOptions) {
+    this.collection = BluebirdPromise.promisifyAll(new Nedb(options)) as Nedb.NedbAsync;
+  }
+
+  find(query: any, sortKeys?: any, count?: number): BluebirdPromise<any> {
+    const q = this.collection.find(query).sort(sortKeys).limit(count);
+    return BluebirdPromise.promisify(q.exec, { context: q })();
+  }
+
+  findOne(query: any): BluebirdPromise<any> {
+    return this.collection.findOneAsync(query);
+  }
+
+  update(query: any, updateQuery: any, options?: any): BluebirdPromise<any> {
+    return this.collection.updateAsync(query, updateQuery, options);
+  }
+
+  remove(query: any): BluebirdPromise<any> {
+    return this.collection.removeAsync(query);
+  }
+
+  insert(document: any): BluebirdPromise<any> {
+    return this.collection.insertAsync(document);
+  }
+
+  count(query: any): BluebirdPromise<number> {
+    return this.collection.countAsync(query);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/nedb/NedbCollectionFactory.spec.ts b/themes/squares/server/src/lib/storage/nedb/NedbCollectionFactory.spec.ts
new file mode 100644
index 00000000..da90c661
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/nedb/NedbCollectionFactory.spec.ts
@@ -0,0 +1,16 @@
+import Sinon = require("sinon");
+import Assert = require("assert");
+
+import { NedbCollectionFactory } from "./NedbCollectionFactory";
+
+describe("storage/nedb/NedbCollectionFactory", function() {
+  it("should create a nedb collection", function() {
+    const nedbOptions = {
+      inMemoryOnly: true
+    };
+    const factory = new NedbCollectionFactory(nedbOptions);
+
+    const collection = factory.build("mycollection");
+    Assert(collection);
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/storage/nedb/NedbCollectionFactory.ts b/themes/squares/server/src/lib/storage/nedb/NedbCollectionFactory.ts
new file mode 100644
index 00000000..49c4dc85
--- /dev/null
+++ b/themes/squares/server/src/lib/storage/nedb/NedbCollectionFactory.ts
@@ -0,0 +1,28 @@
+import { ICollection } from "../ICollection";
+import { ICollectionFactory } from "../ICollectionFactory";
+import { NedbCollection } from "./NedbCollection";
+import path = require("path");
+import Nedb = require("nedb");
+
+export interface NedbOptions {
+  inMemoryOnly?: boolean;
+  directory?: string;
+}
+
+export class NedbCollectionFactory implements ICollectionFactory {
+  private options: Nedb.DataStoreOptions;
+
+  constructor(options: Nedb.DataStoreOptions) {
+    this.options = options;
+  }
+
+  build(collectionName: string): ICollection {
+    const datastoreOptions: Nedb.DataStoreOptions = {
+      inMemoryOnly: this.options.inMemoryOnly || false,
+      autoload: true,
+      filename: (this.options.filename) ? path.resolve(this.options.filename, collectionName) : undefined
+    };
+
+    return new NedbCollection(datastoreOptions);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/stubs/express.spec.ts b/themes/squares/server/src/lib/stubs/express.spec.ts
new file mode 100644
index 00000000..48f15d7e
--- /dev/null
+++ b/themes/squares/server/src/lib/stubs/express.spec.ts
@@ -0,0 +1,103 @@
+
+import sinon = require("sinon");
+import express = require("express");
+
+export interface RequestMock {
+    app?: any;
+    body?: any;
+    session?: any;
+    headers?: any;
+    get?: any;
+    query?: any;
+    originalUrl: string;
+}
+
+export interface ResponseMock {
+    send: sinon.SinonStub | sinon.SinonSpy;
+    sendStatus: sinon.SinonStub;
+    sendFile: sinon.SinonStub;
+    sendfile: sinon.SinonStub;
+    status: sinon.SinonStub | sinon.SinonSpy;
+    json: sinon.SinonStub | sinon.SinonSpy;
+    links: sinon.SinonStub;
+    jsonp: sinon.SinonStub;
+    download: sinon.SinonStub;
+    contentType: sinon.SinonStub;
+    type: sinon.SinonStub;
+    format: sinon.SinonStub;
+    attachment: sinon.SinonStub;
+    set: sinon.SinonStub;
+    header: sinon.SinonStub;
+    headersSent: boolean;
+    get: sinon.SinonStub;
+    clearCookie: sinon.SinonStub;
+    cookie: sinon.SinonStub;
+    location: sinon.SinonStub;
+    redirect: sinon.SinonStub | sinon.SinonSpy;
+    render: sinon.SinonStub | sinon.SinonSpy;
+    locals: sinon.SinonStub;
+    charset: string;
+    vary: sinon.SinonStub;
+    app: any;
+    write: sinon.SinonStub;
+    writeContinue: sinon.SinonStub;
+    writeHead: sinon.SinonStub;
+    statusCode: number;
+    statusMessage: string;
+    setHeader: sinon.SinonStub;
+    setTimeout: sinon.SinonStub;
+    sendDate: boolean;
+    getHeader: sinon.SinonStub;
+}
+
+export function RequestMock(): RequestMock {
+    return {
+        originalUrl: "/non-api/xxx",
+        app: {
+            get: sinon.stub()
+        },
+        headers: {
+            "x-forwarded-for": "127.0.0.1"
+        },
+        session: {}
+    };
+}
+export function ResponseMock(): ResponseMock {
+    return {
+        send: sinon.stub(),
+        status: sinon.stub(),
+        json: sinon.stub(),
+        sendStatus: sinon.stub(),
+        links: sinon.stub(),
+        jsonp: sinon.stub(),
+        sendFile: sinon.stub(),
+        sendfile: sinon.stub(),
+        download: sinon.stub(),
+        contentType: sinon.stub(),
+        type: sinon.stub(),
+        format: sinon.stub(),
+        attachment: sinon.stub(),
+        set: sinon.stub(),
+        header: sinon.stub(),
+        headersSent: true,
+        get: sinon.stub(),
+        clearCookie: sinon.stub(),
+        cookie: sinon.stub(),
+        location: sinon.stub(),
+        redirect: sinon.stub(),
+        render: sinon.stub(),
+        locals: sinon.stub(),
+        charset: "utf-8",
+        vary: sinon.stub(),
+        app: sinon.stub(),
+        write: sinon.stub(),
+        writeContinue: sinon.stub(),
+        writeHead: sinon.stub(),
+        statusCode: 200,
+        statusMessage: "message",
+        setHeader: sinon.stub(),
+        setTimeout: sinon.stub(),
+        sendDate: true,
+        getHeader: sinon.stub()
+    };
+}
diff --git a/themes/squares/server/src/lib/stubs/ldapjs.spec.ts b/themes/squares/server/src/lib/stubs/ldapjs.spec.ts
new file mode 100644
index 00000000..045c0e11
--- /dev/null
+++ b/themes/squares/server/src/lib/stubs/ldapjs.spec.ts
@@ -0,0 +1,50 @@
+
+import Sinon = require("sinon");
+
+export class LdapjsMock {
+    createClientStub: sinon.SinonStub;
+
+    constructor() {
+        this.createClientStub = Sinon.stub();
+    }
+
+    createClient(params: any) {
+        return this.createClientStub(params);
+    }
+}
+
+export class LdapjsClientMock {
+    bindStub: sinon.SinonStub;
+    unbindStub: sinon.SinonStub;
+    searchStub: sinon.SinonStub;
+    modifyStub: sinon.SinonStub;
+    onStub: sinon.SinonStub;
+
+    constructor() {
+        this.bindStub = Sinon.stub();
+        this.unbindStub = Sinon.stub();
+        this.searchStub = Sinon.stub();
+        this.modifyStub = Sinon.stub();
+        this.onStub = Sinon.stub();
+    }
+
+    bind() {
+        return this.bindStub();
+    }
+
+    unbind() {
+        return this.unbindStub();
+    }
+
+    search() {
+        return this.searchStub();
+    }
+
+    modify() {
+        return this.modifyStub();
+    }
+
+    on() {
+        return this.onStub();
+    }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/stubs/speakeasy.spec.ts b/themes/squares/server/src/lib/stubs/speakeasy.spec.ts
new file mode 100644
index 00000000..023614dc
--- /dev/null
+++ b/themes/squares/server/src/lib/stubs/speakeasy.spec.ts
@@ -0,0 +1,7 @@
+
+import sinon = require("sinon");
+
+export = {
+    totp: sinon.stub(),
+    generateSecret: sinon.stub()
+};
diff --git a/themes/squares/server/src/lib/stubs/u2f.spec.ts b/themes/squares/server/src/lib/stubs/u2f.spec.ts
new file mode 100644
index 00000000..234b28c1
--- /dev/null
+++ b/themes/squares/server/src/lib/stubs/u2f.spec.ts
@@ -0,0 +1,16 @@
+
+import sinon = require("sinon");
+
+export interface U2FMock {
+    request: sinon.SinonStub;
+    checkSignature: sinon.SinonStub;
+    checkRegistration: sinon.SinonStub;
+}
+
+export function U2FMock(): U2FMock {
+    return {
+        request: sinon.stub(),
+        checkSignature: sinon.stub(),
+        checkRegistration: sinon.stub()
+    };
+}
diff --git a/themes/squares/server/src/lib/utils/HashGenerator.spec.ts b/themes/squares/server/src/lib/utils/HashGenerator.spec.ts
new file mode 100644
index 00000000..f19619a6
--- /dev/null
+++ b/themes/squares/server/src/lib/utils/HashGenerator.spec.ts
@@ -0,0 +1,18 @@
+import Assert = require("assert");
+import { HashGenerator } from "./HashGenerator";
+
+describe("utils/HashGenerator", function () {
+  it("should compute correct ssha512 (password)", function () {
+    return HashGenerator.ssha512("password", 500000, "jgiCMRyGXzoqpxS3")
+      .then(function (hash: string) {
+        Assert.equal(hash, "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/");
+      });
+  });
+
+  it("should compute correct ssha512 (test)", function () {
+    return HashGenerator.ssha512("test", 500000, "abcdefghijklmnop")
+      .then(function (hash: string) {
+        Assert.equal(hash, "{CRYPT}$6$rounds=500000$abcdefghijklmnop$sTlNGf0VO/HTQIOXemmaBbV28HUch/qhWOA1/4dsDj6CDQYhUgXbYSPL6gccAsWMr2zD5fFWwhKmPdG.yxphs.");
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/utils/HashGenerator.ts b/themes/squares/server/src/lib/utils/HashGenerator.ts
new file mode 100644
index 00000000..e67de32b
--- /dev/null
+++ b/themes/squares/server/src/lib/utils/HashGenerator.ts
@@ -0,0 +1,23 @@
+import BluebirdPromise = require("bluebird");
+import RandomString = require("randomstring");
+import Util = require("util");
+const crypt = require("crypt3");
+
+export class HashGenerator {
+  static ssha512(
+    password: string,
+    rounds: number = 500000,
+    salt?: string): BluebirdPromise<string> {
+    const saltSize = 16;
+    // $6 means SHA512
+    const _salt = Util.format("$6$rounds=%d$%s", rounds,
+      (salt) ? salt : RandomString.generate(16));
+
+    const cryptAsync = BluebirdPromise.promisify<string, string, string>(crypt);
+
+    return cryptAsync(password, _salt)
+      .then(function (hash: string) {
+        return BluebirdPromise.resolve(Util.format("{CRYPT}%s", hash));
+      });
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/utils/ObjectCloner.ts b/themes/squares/server/src/lib/utils/ObjectCloner.ts
new file mode 100644
index 00000000..3e125d74
--- /dev/null
+++ b/themes/squares/server/src/lib/utils/ObjectCloner.ts
@@ -0,0 +1,6 @@
+
+export class ObjectCloner {
+  static clone(obj: any): any {
+    return JSON.parse(JSON.stringify(obj));
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/utils/SafeRedirection.spec.ts b/themes/squares/server/src/lib/utils/SafeRedirection.spec.ts
new file mode 100644
index 00000000..4126949f
--- /dev/null
+++ b/themes/squares/server/src/lib/utils/SafeRedirection.spec.ts
@@ -0,0 +1,33 @@
+import Assert = require("assert");
+import Sinon = require("sinon");
+import { SafeRedirector } from "./SafeRedirection";
+
+describe("web_server/middlewares/SafeRedirection", () => {
+  describe("Url is in protected domain", () => {
+    before(() => {
+      this.redirector = new SafeRedirector("example.com");
+      this.res = {redirect: Sinon.stub()};
+    });
+
+    it("should redirect to provided url", () => {
+      this.redirector.redirectOrElse(this.res,
+        "https://mysubdomain.example.com:8080/abc",
+        "https://authelia.example.com");
+      Assert(this.res.redirect.calledWith("https://mysubdomain.example.com:8080/abc"));
+    });
+
+    it("should redirect to default url when wrong domain", () => {
+      this.redirector.redirectOrElse(this.res,
+        "https://mysubdomain.domain.rtf:8080/abc",
+        "https://authelia.example.com");
+      Assert(this.res.redirect.calledWith("https://authelia.example.com"));
+    });
+
+    it("should redirect to default url when not terminating by domain", () => {
+      this.redirector.redirectOrElse(this.res,
+        "https://mysubdomain.example.com.rtf:8080/abc",
+        "https://authelia.example.com");
+      Assert(this.res.redirect.calledWith("https://authelia.example.com"));
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/utils/SafeRedirection.ts b/themes/squares/server/src/lib/utils/SafeRedirection.ts
new file mode 100644
index 00000000..9e6a32e0
--- /dev/null
+++ b/themes/squares/server/src/lib/utils/SafeRedirection.ts
@@ -0,0 +1,22 @@
+import Express = require("express");
+import { DomainExtractor } from "../../../../shared/DomainExtractor";
+import { BelongToDomain } from "../../../../shared/BelongToDomain";
+
+
+export class SafeRedirector {
+  private domain: string;
+
+  constructor(domain: string) {
+    this.domain = domain;
+  }
+
+  redirectOrElse(
+    res: Express.Response,
+    url: string,
+    defaultUrl: string): void {
+    if (BelongToDomain(url, this.domain)) {
+        res.redirect(url);
+      }
+      res.redirect(defaultUrl);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/utils/URLDecomposer.spec.ts b/themes/squares/server/src/lib/utils/URLDecomposer.spec.ts
new file mode 100644
index 00000000..cbb03873
--- /dev/null
+++ b/themes/squares/server/src/lib/utils/URLDecomposer.spec.ts
@@ -0,0 +1,46 @@
+import { URLDecomposer } from "./URLDecomposer";
+import Assert = require("assert");
+
+describe("utils/URLDecomposer", function () {
+  describe("test fromUrl", function () {
+    it("should return domain from https url", function () {
+      const d = URLDecomposer.fromUrl("https://www.example.com/test/abc");
+      Assert.equal(d.domain, "www.example.com");
+      Assert.equal(d.path, "/test/abc");
+    });
+
+    it("should return domain from http url", function () {
+      const d = URLDecomposer.fromUrl("http://www.example.com/test/abc");
+      Assert.equal(d.domain, "www.example.com");
+      Assert.equal(d.path, "/test/abc");
+    });
+
+    it("should return domain when url contains port", function () {
+      const d = URLDecomposer.fromUrl("https://www.example.com:8080/test/abc");
+      Assert.equal(d.domain, "www.example.com");
+      Assert.equal(d.path, "/test/abc");
+    });
+
+    it("should return default path when no path provided", function () {
+      const d = URLDecomposer.fromUrl("https://www.example.com:8080");
+      Assert.equal(d.domain, "www.example.com");
+      Assert.equal(d.path, "/");
+    });
+
+    it("should return default path when provided", function () {
+      const d = URLDecomposer.fromUrl("https://www.example.com:8080/");
+      Assert.equal(d.domain, "www.example.com");
+      Assert.equal(d.path, "/");
+    });
+
+    it("should return undefined when does not match", function () {
+      const d = URLDecomposer.fromUrl("https:///abc/test");
+      Assert.equal(d, undefined);
+    });
+
+    it("should return undefined when does not match", function () {
+      const d = URLDecomposer.fromUrl("https:///abc/test");
+      Assert.equal(d, undefined);
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/utils/URLDecomposer.ts b/themes/squares/server/src/lib/utils/URLDecomposer.ts
new file mode 100644
index 00000000..9bdf2e9d
--- /dev/null
+++ b/themes/squares/server/src/lib/utils/URLDecomposer.ts
@@ -0,0 +1,15 @@
+export class URLDecomposer {
+  static fromUrl(url: string): {domain: string, path: string} {
+    if (!url) return;
+    const match = url.match(/https?:\/\/([a-z0-9_.-]+)(:[0-9]+)?(.*)/);
+
+    if (!match) return;
+
+    if (match[1] && !match[3]) {
+      return {domain: match[1], path: "/"};
+    } else if (match[1] && match[3]) {
+      return {domain: match[1], path: match[3]};
+    }
+    return;
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/web_server/Configurator.ts b/themes/squares/server/src/lib/web_server/Configurator.ts
new file mode 100644
index 00000000..6e404874
--- /dev/null
+++ b/themes/squares/server/src/lib/web_server/Configurator.ts
@@ -0,0 +1,47 @@
+import { Configuration } from "../configuration/schema/Configuration";
+import { GlobalDependencies } from "../../../types/Dependencies";
+import { SessionConfigurationBuilder } from
+  "../configuration/SessionConfigurationBuilder";
+import Path = require("path");
+import Express = require("express");
+import * as BodyParser from "body-parser";
+import { RestApi } from "./RestApi";
+import { WithHeadersLogged } from "./middlewares/WithHeadersLogged";
+import { ServerVariables } from "../ServerVariables";
+import Helmet = require("helmet");
+
+const addRequestId = require("express-request-id")();
+
+// Constants
+const TRUST_PROXY = "trust proxy";
+const X_POWERED_BY = "x-powered-by";
+const VIEWS = "views";
+const VIEW_ENGINE = "view engine";
+const PUG = "pug";
+
+export class Configurator {
+  static configure(config: Configuration,
+    app: Express.Application,
+    vars: ServerVariables,
+    deps: GlobalDependencies): void {
+    const viewsDirectory = Path.resolve(__dirname, "../../views");
+    const publicHtmlDirectory = Path.resolve(__dirname, "../../public_html");
+
+    const expressSessionOptions = SessionConfigurationBuilder.build(config, deps);
+
+    app.use(Express.static(publicHtmlDirectory));
+    app.use(BodyParser.urlencoded({ extended: false }));
+    app.use(BodyParser.json());
+    app.use(deps.session(expressSessionOptions));
+    app.use(addRequestId);
+    app.use(WithHeadersLogged.middleware(vars.logger));
+    app.disable(X_POWERED_BY);
+    app.enable(TRUST_PROXY);
+    app.use(Helmet());
+
+    app.set(VIEWS, viewsDirectory);
+    app.set(VIEW_ENGINE, PUG);
+
+    RestApi.setup(app, vars);
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/web_server/RestApi.ts b/themes/squares/server/src/lib/web_server/RestApi.ts
new file mode 100644
index 00000000..9144a15b
--- /dev/null
+++ b/themes/squares/server/src/lib/web_server/RestApi.ts
@@ -0,0 +1,125 @@
+import Express = require("express");
+
+import FirstFactorGet = require("../routes/firstfactor/get");
+import SecondFactorGet = require("../routes/secondfactor/get");
+
+import FirstFactorPost = require("../routes/firstfactor/post");
+import LogoutGet = require("../routes/logout/get");
+import VerifyGet = require("../routes/verify/get");
+import TOTPSignGet = require("../routes/secondfactor/totp/sign/post");
+
+import IdentityCheckMiddleware = require("../IdentityCheckMiddleware");
+
+import TOTPRegistrationIdentityHandler from "../routes/secondfactor/totp/identity/RegistrationHandler";
+import U2FRegistrationIdentityHandler from "../routes/secondfactor/u2f/identity/RegistrationHandler";
+import ResetPasswordIdentityHandler from "../routes/password-reset/identity/PasswordResetHandler";
+
+import U2FSignPost = require("../routes/secondfactor/u2f/sign/post");
+import U2FSignRequestGet = require("../routes/secondfactor/u2f/sign_request/get");
+
+import U2FRegisterPost = require("../routes/secondfactor/u2f/register/post");
+import U2FRegisterRequestGet = require("../routes/secondfactor/u2f/register_request/get");
+
+import ResetPasswordFormPost = require("../routes/password-reset/form/post");
+import ResetPasswordRequestPost = require("../routes/password-reset/request/get");
+
+import Error401Get = require("../routes/error/401/get");
+import Error403Get = require("../routes/error/403/get");
+import Error404Get = require("../routes/error/404/get");
+
+import LoggedIn = require("../routes/loggedin/get");
+
+import { ServerVariables } from "../ServerVariables";
+import Endpoints = require("../../../../shared/api");
+import { RequireValidatedFirstFactor } from "./middlewares/RequireValidatedFirstFactor";
+
+function setupTotp(app: Express.Application, vars: ServerVariables) {
+  app.post(Endpoints.SECOND_FACTOR_TOTP_POST,
+    RequireValidatedFirstFactor.middleware(vars.logger),
+    TOTPSignGet.default(vars));
+
+  app.get(Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger));
+
+  app.get(Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger));
+
+  IdentityCheckMiddleware.register(app,
+    Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
+    Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET,
+    new TOTPRegistrationIdentityHandler(vars.logger,
+      vars.userDataStore, vars.totpHandler, vars.config.totp),
+    vars);
+}
+
+function setupU2f(app: Express.Application, vars: ServerVariables) {
+  app.get(Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger),
+    U2FSignRequestGet.default(vars));
+
+  app.post(Endpoints.SECOND_FACTOR_U2F_SIGN_POST,
+    RequireValidatedFirstFactor.middleware(vars.logger),
+    U2FSignPost.default(vars));
+
+  app.get(Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger),
+    U2FRegisterRequestGet.default(vars));
+
+  app.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST,
+    RequireValidatedFirstFactor.middleware(vars.logger),
+    U2FRegisterPost.default(vars));
+
+  app.get(Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger));
+
+  app.get(Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger));
+
+  IdentityCheckMiddleware.register(app,
+    Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET,
+    Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET,
+    new U2FRegistrationIdentityHandler(vars.logger), vars);
+}
+
+function setupResetPassword(app: Express.Application, vars: ServerVariables) {
+  IdentityCheckMiddleware.register(app,
+    Endpoints.RESET_PASSWORD_IDENTITY_START_GET,
+    Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET,
+    new ResetPasswordIdentityHandler(vars.logger, vars.usersDatabase),
+    vars);
+
+  app.get(Endpoints.RESET_PASSWORD_REQUEST_GET,
+    ResetPasswordRequestPost.default);
+  app.post(Endpoints.RESET_PASSWORD_FORM_POST,
+    ResetPasswordFormPost.default(vars));
+}
+
+function setupErrors(app: Express.Application, vars: ServerVariables) {
+  app.get(Endpoints.ERROR_401_GET, Error401Get.default(vars));
+  app.get(Endpoints.ERROR_403_GET, Error403Get.default(vars));
+  app.get(Endpoints.ERROR_404_GET, Error404Get.default);
+}
+
+export class RestApi {
+  static setup(app: Express.Application, vars: ServerVariables): void {
+    app.get(Endpoints.FIRST_FACTOR_GET, FirstFactorGet.default(vars));
+
+    app.get(Endpoints.SECOND_FACTOR_GET,
+      RequireValidatedFirstFactor.middleware(vars.logger),
+      SecondFactorGet.default(vars));
+
+    app.get(Endpoints.LOGOUT_GET, LogoutGet.default(vars));
+
+    app.get(Endpoints.VERIFY_GET, VerifyGet.default(vars));
+    app.post(Endpoints.FIRST_FACTOR_POST, FirstFactorPost.default(vars));
+
+    setupTotp(app, vars);
+    setupU2f(app, vars);
+    setupResetPassword(app, vars);
+    setupErrors(app, vars);
+
+    app.get(Endpoints.LOGGED_IN,
+      RequireValidatedFirstFactor.middleware(vars.logger),
+      LoggedIn.default(vars));
+  }
+}
diff --git a/themes/squares/server/src/lib/web_server/middlewares/RequireValidatedFirstFactor.ts b/themes/squares/server/src/lib/web_server/middlewares/RequireValidatedFirstFactor.ts
new file mode 100644
index 00000000..ecfd7576
--- /dev/null
+++ b/themes/squares/server/src/lib/web_server/middlewares/RequireValidatedFirstFactor.ts
@@ -0,0 +1,27 @@
+import Express = require("express");
+import BluebirdPromise = require("bluebird");
+import ErrorReplies = require("../../ErrorReplies");
+import { IRequestLogger } from "../../logging/IRequestLogger";
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import Exceptions = require("../../Exceptions");
+import { Level } from "../../authentication/Level";
+
+export class RequireValidatedFirstFactor {
+  static middleware(logger: IRequestLogger) {
+    return function (req: Express.Request, res: Express.Response,
+      next: Express.NextFunction): BluebirdPromise<void> {
+
+      return new BluebirdPromise<void>(function (resolve, reject) {
+        const authSession = AuthenticationSessionHandler.get(req, logger);
+        if (!authSession.userid || authSession.authentication_level < Level.ONE_FACTOR)
+          return reject(
+            new Exceptions.FirstFactorValidationError(
+              "First factor has not been validated yet."));
+
+        next();
+        resolve();
+      })
+        .catch(ErrorReplies.replyWithError401(req, res, logger));
+    };
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/lib/web_server/middlewares/WithHeadersLogged.ts b/themes/squares/server/src/lib/web_server/middlewares/WithHeadersLogged.ts
new file mode 100644
index 00000000..139db114
--- /dev/null
+++ b/themes/squares/server/src/lib/web_server/middlewares/WithHeadersLogged.ts
@@ -0,0 +1,12 @@
+import Express = require("express");
+import { IRequestLogger } from "../../logging/IRequestLogger";
+
+export class WithHeadersLogged {
+  static middleware(logger: IRequestLogger) {
+    return function (req: Express.Request, res: Express.Response,
+      next: Express.NextFunction): void {
+      logger.debug(req, "Headers = %s", JSON.stringify(req.headers));
+      next();
+    };
+  }
+}
\ No newline at end of file
diff --git a/themes/squares/server/src/resources/email-template.ejs b/themes/squares/server/src/resources/email-template.ejs
new file mode 100644
index 00000000..f59c2f94
--- /dev/null
+++ b/themes/squares/server/src/resources/email-template.ejs
@@ -0,0 +1,254 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+   <head>
+      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+      <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+      <title>Simples-Minimalistic Responsive Template</title>
+      
+      <style type="text/css">
+         /* Client-specific Styles */
+         #outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
+         body{background: rgb(0, 0, 0);width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
+         /* Prevent Webkit and Windows Mobile platforms from changing default font sizes, while not breaking desktop design. */
+         .ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
+         .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;} /* Force Hotmail to display normal line spacing.*/
+         #backgroundTable {margin:0; padding:0; width:100% !important; line-height: 100% !important;}
+         img {outline:none; text-decoration:none;border:none; -ms-interpolation-mode: bicubic;}
+         a img {border:none;}
+         .image_fix {display:block;}
+         p {margin: 0px 0px !important;}
+         table td {border-collapse: collapse;}
+         table { border-collapse:collapse; mso-table-lspace:0pt; mso-table-rspace:0pt; }
+         a {color: #ffffff;text-decoration: none;text-decoration:none!important;}
+          h1 {color: #ffffff; line-height: 30px; }
+         .button {padding: 15px 30px; border-radius: 10px; background: rgb(3, 183, 3); text-decoration:none; }
+
+         /*STYLES*/
+         table[class=full] { width: 100%; clear: both; }
+         /*IPAD STYLES*/
+         @media only screen and (max-width: 640px) {
+         a[href^="tel"], a[href^="sms"] {
+         text-decoration: none;
+         color: #ffffff; /* or whatever your want */
+         pointer-events: none;
+         cursor: default;
+         }
+         .mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
+         text-decoration: default;
+         color: #000000 !important;
+         pointer-events: auto;
+         cursor: default;
+         }
+         table[class=devicewidth] {width: 440px!important;text-align:center!important;}
+         table[class=devicewidthinner] {width: 420px!important;text-align:center!important;}
+         img[class=banner] {width: 440px!important;height:220px!important;}
+         img[class=colimg2] {width: 440px!important;height:220px!important;}
+         
+         }
+         /*IPHONE STYLES*/
+         @media only screen and (max-width: 480px) {
+         a[href^="tel"], a[href^="sms"] {
+         text-decoration: none;
+         color: #000000; /* or whatever your want */
+         pointer-events: none;
+         cursor: default;
+         }
+         .mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
+         text-decoration: default;
+         color: #000000 !important; 
+         pointer-events: auto;
+         cursor: default;
+         }
+         table[class=devicewidth] {width: 280px!important;text-align:center!important;}
+         table[class=devicewidthinner] {width: 260px!important;text-align:center!important;}
+         img[class=banner] {width: 280px!important;height:140px!important;}
+         img[class=colimg2] {width: 280px!important;height:140px!important;}
+         td[class=mobile-hide]{display:none!important;}
+         td[class="padding-bottom25"]{padding-bottom:25px!important;}
+        
+         }
+      </style>
+   </head>
+   <body>
+<!-- Start of header -->
+<table width="100%" bgcolor="#000000" cellpadding="0" cellspacing="0" border="0" id="backgroundTable" st-sortable="header">
+   <tbody>
+      <tr>
+         <td>
+            <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+               <tbody>
+                  <tr>
+                     <td width="100%">
+                        <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+                           <tbody>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td height="20" style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">&nbsp;</td>
+                              </tr>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td>
+                                    <!-- logo -->
+                                    <table width="140" align="center" border="0" cellpadding="0" cellspacing="0" class="devicewidth">
+                                       <tbody>
+                                          <tr>
+                                             <td width="300" height="50" align="center">
+                                              	<h1><%= title %></h1>
+                                             </td>
+                                          </tr>
+                                       </tbody>
+                                    </table>
+                                    <!-- end of logo -->
+                                 </td>
+                              </tr>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td height="20" style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">&nbsp;</td>
+                              </tr>
+                              <!-- Spacing -->
+                           </tbody>
+                        </table>
+                     </td>
+                  </tr>
+               </tbody>
+            </table>
+         </td>
+      </tr>
+   </tbody>
+</table>
+<!-- End of Header -->
+<!-- Start of seperator -->
+<table width="100%" bgcolor="#000000" cellpadding="0" cellspacing="0" border="0" id="backgroundTable" st-sortable="seperator">
+   <tbody>
+      <tr>
+         <td>
+            <table width="600" align="center" cellspacing="0" cellpadding="0" border="0" class="devicewidth">
+               <tbody>
+                  <tr>
+                     <td align="center" height="20" style="font-size:1px; line-height:1px;">&nbsp;</td>
+                  </tr>
+               </tbody>
+            </table>
+         </td>
+      </tr>
+   </tbody>
+</table>
+<!-- End of seperator -->   
+<!-- Start Full Text -->
+<table width="100%" bgcolor="#000000" cellpadding="0" cellspacing="0" border="0" id="backgroundTable" st-sortable="full-text">
+   <tbody>
+      <tr>
+         <td>
+            <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+               <tbody>
+                  <tr>
+                     <td width="100%">
+                        <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+                           <tbody>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td height="20" style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">&nbsp;</td>
+                              </tr>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td>
+                                    <table width="560" align="center" cellpadding="0" cellspacing="0" border="0" class="devicewidthinner">
+                                       <tbody>
+                                          <!-- Title -->
+                                          <tr>
+                                             <td style="font-family: Helvetica, arial, sans-serif; font-size: 16px; color: #ffffff; text-align:center; line-height: 30px;" st-title="fulltext-content">
+                                                This email has been sent to you in order to validate your identity. Please ignore it if you do not know why you received it.
+                                             </td>
+                                          </tr>
+                                          <!-- End of Title -->
+                                          <!-- spacing -->
+                                          <tr>
+                                             <td width="100%" height="20" style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">&nbsp;</td>
+                                          </tr>
+                                          <!-- End of spacing -->
+                                          <!-- content -->
+                                          <tr>
+                                             <td style="font-family: Helvetica, arial, sans-serif; font-size: 16px; color: #666666; text-align:center; line-height: 30px;" st-content="fulltext-content">
+                                                 <a href="<%= url %>" class="button"><%= button_title %></a>
+                                             </td>
+                                          </tr>
+                                          <!-- End of content -->
+                                       </tbody>
+                                    </table>
+                                 </td>
+                              </tr>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td height="20" style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">&nbsp;</td>
+                              </tr>
+                              <!-- Spacing -->
+                           </tbody>
+                        </table>
+                     </td>
+                  </tr>
+               </tbody>
+            </table>
+         </td>
+      </tr>
+   </tbody>
+</table>
+<!-- end of full text -->
+<!-- Start of seperator -->
+<table width="100%" bgcolor="#000000" cellpadding="0" cellspacing="0" border="0" id="backgroundTable" st-sortable="seperator">
+   <tbody>
+      <tr>
+         <td>
+            <table width="600" align="center" cellspacing="0" cellpadding="0" border="0" class="devicewidth">
+               <tbody>
+                  <tr>
+                     <td align="center" height="30" style="font-size:1px; line-height:1px;">&nbsp;</td>
+                  </tr>
+                  <tr>
+                     <td width="550" align="center" height="1" bgcolor="#d1d1d1" style="font-size:1px; line-height:1px;">&nbsp;</td>
+                  </tr>
+                  <tr>
+                     <td align="center" height="30" style="font-size:1px; line-height:1px;">&nbsp;</td>
+                  </tr>
+               </tbody>
+            </table>
+         </td>
+      </tr>
+   </tbody>
+</table>
+<!-- End of seperator -->  
+<!-- Start of Postfooter -->
+<table width="100%" bgcolor="#000000" cellpadding="0" cellspacing="0" border="0" id="backgroundTable" st-sortable="postfooter" >
+   <tbody>
+      <tr>
+         <td>
+            <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+               <tbody>
+                  <tr>
+                     <td width="100%">
+                        <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+                           <tbody>
+                              <tr>
+                                 <td align="center" valign="middle" style="font-family: Helvetica, arial, sans-serif; font-size: 14px;color: #ffffff" st-content="postfooter">
+									Please ignore this email if you did not initiate the process.
+                                 </td>
+                              </tr>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td width="100%" height="20"></td>
+                              </tr>
+                              <!-- Spacing -->
+                           </tbody>
+                        </table>
+                     </td>
+                  </tr>
+               </tbody>
+            </table>
+         </td>
+      </tr>
+   </tbody>
+</table>
+<!-- End of postfooter -->
+   
+   </body>
+   </html>
+
diff --git a/themes/squares/server/src/views/already-logged-in.pug b/themes/squares/server/src/views/already-logged-in.pug
new file mode 100644
index 00000000..137bbea3
--- /dev/null
+++ b/themes/squares/server/src/views/already-logged-in.pug
@@ -0,0 +1,14 @@
+extends layout/layout.pug 
+
+block form-header
+  h1 Sign in
+
+block content
+  img(class="header-img" src="/img/success.png" alt="success")
+  if redirection_url
+    p You are already logged in as <b>#{ username }</b>.<br/><br/>
+      | If you are not redirected in few seconds, click <a href="#{ redirection_url }">here</a>.<br/><br/>
+      | Otherwise, click <a href="#{ logout_endpoint }">here</a> to log off.
+  else
+    p You are already logged in as <b>#{ username }</b>.<br/><br/>
+      | Click <a href="#{ logout_endpoint }">here</a> to log off.
diff --git a/themes/squares/server/src/views/errors/.directory b/themes/squares/server/src/views/errors/.directory
new file mode 100644
index 00000000..33f71bea
--- /dev/null
+++ b/themes/squares/server/src/views/errors/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,59,57
+Version=3
+ViewMode=1
diff --git a/themes/squares/server/src/views/errors/401.pug b/themes/squares/server/src/views/errors/401.pug
new file mode 100644
index 00000000..b7a222ad
--- /dev/null
+++ b/themes/squares/server/src/views/errors/401.pug
@@ -0,0 +1,16 @@
+extends ../layout/layout.pug
+
+block variables
+  - page_classname = "error-401";
+
+block form-header
+  h1 Error 401
+
+block content
+  img(class="header-img" src="/img/warning.png" alt="warning")
+  if redirection_url
+    p You are not authorized to access this resource.<br/><br/>
+      | Please click <a href=#{redirection_url}>here</a> if you are not 
+      | redirected in few seconds.
+  else
+    p You are not authorized to access this resource.
\ No newline at end of file
diff --git a/themes/squares/server/src/views/errors/403.pug b/themes/squares/server/src/views/errors/403.pug
new file mode 100644
index 00000000..f4b5ca8a
--- /dev/null
+++ b/themes/squares/server/src/views/errors/403.pug
@@ -0,0 +1,16 @@
+extends ../layout/layout.pug
+
+block variables
+  - page_classname = "error-403";
+
+block form-header
+  h1 Error 403
+
+block content
+  img(class="header-img" src="/img/warning.png" alt="warning")
+  if redirection_url
+    p You don't have enough privileges to access this resource.<br/><br/>
+      | Please click <a href=#{redirection_url}>here</a> if you are not 
+      | redirected in few seconds.
+  else
+    p You don't have enough privileges to access this resource.
diff --git a/themes/squares/server/src/views/errors/404.pug b/themes/squares/server/src/views/errors/404.pug
new file mode 100644
index 00000000..06d6375f
--- /dev/null
+++ b/themes/squares/server/src/views/errors/404.pug
@@ -0,0 +1,11 @@
+extends ../layout/layout.pug
+
+block variables
+  - page_classname = "error-404";
+
+block form-header
+  <h1>Error 404</h1>
+
+block content
+  img(class="header-img" src="/img/warning.png" alt="warning")
+  p Page not found.
diff --git a/themes/squares/server/src/views/firstfactor.pug b/themes/squares/server/src/views/firstfactor.pug
new file mode 100644
index 00000000..57447071
--- /dev/null
+++ b/themes/squares/server/src/views/firstfactor.pug
@@ -0,0 +1,23 @@
+extends layout/layout.pug 
+
+block variables
+  - page_classname = "firstfactor";
+
+block form-header
+  h1 Sign in
+
+block content
+  div(class="notification")
+  img(class="header-img" src="/img/sharingan.png" alt="user profile")
+  p Enter your credentials to sign in
+  form(class="form-signin")
+    div(class="form-inputs")
+      input(type="text" class="form-control" id="username" placeholder="Username" required autofocus)
+      input(type="password" class="form-control" id="password" placeholder="Password" required)
+    button(id="signin" class="btn btn-lg btn-primary btn-block" type="submit") Sign in
+    div(class="keep-me-logged-in pull-left")
+      input(type="checkbox" id="keep_me_logged_in" name="keep_me_logged_in" value="true")
+      label(for="keep_me_logged_in") Keep me logged in
+    div(class="bottom-right-links pull-right")
+      a(href=reset_password_request_endpoint, class="link forgot-password") Forgot password?
+    span(class="clearfix")
diff --git a/themes/squares/server/src/views/layout/layout.pug b/themes/squares/server/src/views/layout/layout.pug
new file mode 100644
index 00000000..43247436
--- /dev/null
+++ b/themes/squares/server/src/views/layout/layout.pug
@@ -0,0 +1,28 @@
+block variables
+
+doctype html
+html
+  head
+    title Authelia - 2FA
+    meta(name="viewport", content="width=device-width, initial-scale=1.0")
+    meta(name="robots", content="noindex, nofollow, nosnippet, noarchive")
+    meta(http-equiv="Content-Security-Policy", content="default-src 'self'; img-src 'self' data:;")
+    link(rel="icon", href="/img/icon.png" type="image/png" sizes="32x32")
+    link(rel="stylesheet", type="text/css", href="/css/authelia.css")
+    if redirection_url
+      meta(http-equiv="refresh" content="4;url=" + redirection_url) 
+  body
+    div(class="container")
+      div(class="row")
+        div(class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3")
+          div(class="account-wall " + page_classname)
+            div(class="row header")
+              block form-header
+            div(class="row body")
+              div(class="form col-xs-10 col-xs-offset-1 col-sm-8 col-sm-offset-2 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2")
+                block content
+            div(class="row footer poweredby-block")
+              div(class="poweredby col-xs-6 col-xs-offset-4 col-sm-6 col-sm-offset-4 col-md-6 col-md-offset-4")
+                | Powered by <a class="authelia-brand" href="https://github.com/clems4ever/authelia">Authelia</a>
+    block entrypoint
+    script(src="/js/authelia.js", type="text/javascript")
diff --git a/themes/squares/server/src/views/need-identity-validation.pug b/themes/squares/server/src/views/need-identity-validation.pug
new file mode 100644
index 00000000..4cfd6271
--- /dev/null
+++ b/themes/squares/server/src/views/need-identity-validation.pug
@@ -0,0 +1,12 @@
+extends layout/layout.pug
+
+block variables
+  - page_classname = "identity-validation";
+
+block form-header
+  h1 Registration
+
+block content
+  img(class="header-img" src="/img/mail.png" alt="mail")
+  p A confirmation email has been sent to your mailbox. 
+    | Please open it and click on the link within 15 minutes to confirm the registration.
diff --git a/themes/squares/server/src/views/password-reset-form.pug b/themes/squares/server/src/views/password-reset-form.pug
new file mode 100644
index 00000000..fd931189
--- /dev/null
+++ b/themes/squares/server/src/views/password-reset-form.pug
@@ -0,0 +1,18 @@
+extends layout/layout.pug 
+
+block variables
+  - page_classname = "password-reset-form";
+
+block form-header
+  h1 Reset password
+
+block content
+  div(class="notification")
+  img(class="header-img" src="/img/password_white.png" alt="password")
+  p Set your new password and confirm it.
+  form(class="form-signin")
+    div(class="form-inputs")
+      input(class="form-control" type="password" name="password1" id="password1" placeholder="New password" required="required")
+      input(class="form-control" type="password" name="password2" id="password2" placeholder="Password confirmation" required="required")
+    button(id="reset-password-button" class="btn btn-lg btn-primary btn-block" type="submit") Reset Password
+    span(class="clearfix")
diff --git a/themes/squares/server/src/views/password-reset-request.pug b/themes/squares/server/src/views/password-reset-request.pug
new file mode 100644
index 00000000..855b5998
--- /dev/null
+++ b/themes/squares/server/src/views/password-reset-request.pug
@@ -0,0 +1,18 @@
+extends layout/layout.pug 
+
+block variables
+  - page_classname = "password-reset-request";
+
+block form-header
+  h1 Reset password
+
+block content
+  div(class="notification")
+  div
+    img(class="header-img" src="/img/password_white.png" alt="password")
+    p After giving your username, you will receive an email to change your password.
+  form(class="form-signin")
+    div(class="form-inputs")
+      input(type="text" class="form-control" name="username" id="username" placeholder="Your username" required="required")
+    button(id="reset-password-button" class="btn btn-lg btn-primary btn-block" type="submit") Reset Password
+    span(class="clearfix")
diff --git a/themes/squares/server/src/views/secondfactor.pug b/themes/squares/server/src/views/secondfactor.pug
new file mode 100644
index 00000000..87b57818
--- /dev/null
+++ b/themes/squares/server/src/views/secondfactor.pug
@@ -0,0 +1,31 @@
+extends layout/layout.pug
+
+block variables
+  - page_classname = "secondfactor";
+
+block form-header
+  h1 Sign in
+
+block content
+  div
+    div(class="notification notification-totp")
+    h3 Hi <b>#{username}</b>
+  div(class="row")
+    div(class="u2f-token")
+      img(src="/img/pendrive.png", alt="security key")
+    p
+      | Please, touch your <a href="https://www.yubico.com/products/yubikey-hardware/fido-u2f-security-key/">security key</a><br/>
+      b Or<br/>
+      | Get a one-time password
+    form(class="form-signin totp")
+      div(class="form-inputs")
+        input(type="text" autocomplete="off" class="form-control" id="token" placeholder="Token" required autofocus)
+      button(class="btn btn-lg btn-primary btn-block totp-button" type="submit") Sign in
+      div(class="pull-right bottom-right-links")
+        div Need to register?
+        div
+          a(href=u2f_identity_start_endpoint, class="link register-u2f", data-toggle="tooltip", title="A security key is required to register.") Security key
+          |  | 
+          a(href=totp_identity_start_endpoint, class="link register-totp") Google Authenticator
+          span(class="clearfix")
+  script(src="/js/u2f-api.js", type="text/javascript")
diff --git a/themes/squares/server/src/views/totp-register.pug b/themes/squares/server/src/views/totp-register.pug
new file mode 100644
index 00000000..1b4d9835
--- /dev/null
+++ b/themes/squares/server/src/views/totp-register.pug
@@ -0,0 +1,25 @@
+extends layout/layout.pug 
+
+block variables
+  - page_classname = "totp-register";
+
+block form-header
+  h1 One-time passwords
+
+block content
+  p Open Google Authenticator and add this entry
+  p(id="secret") #{ base32_secret }
+  p or scan this barcode
+  div(id="qrcode") #{ otpauth_url }
+  p
+    a(href=login_endpoint, id="login-button") Login
+  div(class="need-google-authenticator")
+    | Need Google Authenticator?
+    div(class="store-badges")
+      a(href='https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1', target="_blank")
+        img(alt='Get it on Google Play', src='/img/stores/googleplay-badge.svg', class="store-badge")
+      a(href='https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8', target="_blank")
+        img(alt='Get it on Apple Store' src='/img/stores/applestore-badge.svg' class="store-badge")
+
+block entrypoint
+  script(src="/js/qrcode.min.js", type="text/javascript" )
diff --git a/themes/squares/server/src/views/u2f-register.pug b/themes/squares/server/src/views/u2f-register.pug
new file mode 100644
index 00000000..d52eba6c
--- /dev/null
+++ b/themes/squares/server/src/views/u2f-register.pug
@@ -0,0 +1,12 @@
+extends layout/layout.pug 
+
+block variables
+  - page_classname = "u2f-register";
+
+block form-header
+  h1 Register your security key
+
+block content
+  p Touch the token to register your security key.
+  img(src="/img/pendrive.png" alt="pendrive")
+  script(src="/js/u2f-api.js", type="text/javascript")
\ No newline at end of file
diff --git a/themes/squares/server/test/requests.ts b/themes/squares/server/test/requests.ts
new file mode 100644
index 00000000..93fa0de4
--- /dev/null
+++ b/themes/squares/server/test/requests.ts
@@ -0,0 +1,94 @@
+
+import BluebirdPromise = require("bluebird");
+import request = require("request");
+import assert = require("assert");
+import express = require("express");
+import nodemailer = require("nodemailer");
+import Endpoints = require("../../shared/api");
+
+declare module "request" {
+  export interface RequestAPI<TRequest extends Request,
+      TOptions extends CoreOptions,
+      TUriUrlOptions> {
+      getAsync(uri: string, options?: RequiredUriUrl): BluebirdPromise<RequestResponse>;
+      getAsync(uri: string): BluebirdPromise<RequestResponse>;
+      getAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise<RequestResponse>;
+
+      postAsync(uri: string, options?: CoreOptions): BluebirdPromise<RequestResponse>;
+      postAsync(uri: string): BluebirdPromise<RequestResponse>;
+      postAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise<RequestResponse>;
+  }
+}
+
+const requestAsync: typeof request = BluebirdPromise.promisifyAll(request) as typeof request;
+
+export = function (port: number) {
+  const PORT = port;
+  const BASE_URL = "http://localhost:" + PORT;
+
+  function execute_totp(jar: request.CookieJar, token: string) {
+    return requestAsync.postAsync({
+      url: BASE_URL + Endpoints.SECOND_FACTOR_TOTP_POST,
+      jar: jar,
+      form: {
+        token: token
+      }
+    });
+  }
+
+  function execute_u2f_authentication(jar: request.CookieJar) {
+    return requestAsync.getAsync({
+      url: BASE_URL + Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET,
+      jar: jar
+    })
+      .then(function (res: request.RequestResponse) {
+        assert.equal(res.statusCode, 200);
+        return requestAsync.postAsync({
+          url: BASE_URL + Endpoints.SECOND_FACTOR_U2F_SIGN_POST,
+          jar: jar,
+          form: {
+          }
+        });
+      });
+  }
+
+  function execute_verification(jar: request.CookieJar) {
+    return requestAsync.getAsync({ url: BASE_URL + Endpoints.VERIFY_GET, jar: jar });
+  }
+
+  function execute_login(jar: request.CookieJar) {
+    return requestAsync.getAsync({ url: BASE_URL + Endpoints.FIRST_FACTOR_GET, jar: jar });
+  }
+
+  function execute_first_factor(jar: request.CookieJar) {
+    return requestAsync.postAsync({
+      url: BASE_URL + Endpoints.FIRST_FACTOR_POST,
+      jar: jar,
+      form: {
+        username: "test_ok",
+        password: "password"
+      }
+    });
+  }
+
+  function execute_failing_first_factor(jar: request.CookieJar) {
+    return requestAsync.postAsync({
+      url: BASE_URL + Endpoints.FIRST_FACTOR_POST,
+      jar: jar,
+      form: {
+        username: "test_nok",
+        password: "password"
+      }
+    });
+  }
+
+  return {
+    login: execute_login,
+    verify: execute_verification,
+    u2f_authentication: execute_u2f_authentication,
+    first_factor: execute_first_factor,
+    failing_first_factor: execute_failing_first_factor,
+    totp: execute_totp,
+  };
+};
+
diff --git a/themes/squares/server/tsconfig.json b/themes/squares/server/tsconfig.json
new file mode 100644
index 00000000..ebe98c5e
--- /dev/null
+++ b/themes/squares/server/tsconfig.json
@@ -0,0 +1,21 @@
+{
+    "compilerOptions": {
+        "module": "commonjs",
+        "target": "es6",
+        "moduleResolution": "node",
+        "noImplicitAny": true,
+        "sourceMap": true,
+        "removeComments": true,
+        "outDir": "../dist",
+        "baseUrl": ".",
+        "paths": {
+            "*": [
+                "./types/*",
+                "../shared/types/*"
+            ]
+        }
+    },
+    "exclude": [
+        "src/**/*.spec.ts"
+    ]
+}
diff --git a/themes/squares/server/tslint.json b/themes/squares/server/tslint.json
new file mode 100644
index 00000000..c2c1b750
--- /dev/null
+++ b/themes/squares/server/tslint.json
@@ -0,0 +1,60 @@
+{
+    "rules": {
+        "class-name": true,
+        "comment-format": [
+            true,
+            "check-space"
+        ],
+        "indent": [
+            true,
+            "spaces"
+        ],
+        "one-line": [
+            true,
+            "check-open-brace",
+            "check-whitespace"
+        ],
+        "no-var-keyword": true,
+        "quotemark": [
+            true,
+            "double",
+            "avoid-escape"
+        ],
+        "semicolon": [
+            true,
+            "always",
+            "ignore-bound-class-methods"
+        ],
+        "whitespace": [
+            true,
+            "check-branch",
+            "check-decl",
+            "check-operator",
+            "check-module",
+            "check-separator",
+            "check-type"
+        ],
+        "typedef-whitespace": [
+            true,
+            {
+                "call-signature": "nospace",
+                "index-signature": "nospace",
+                "parameter": "nospace",
+                "property-declaration": "nospace",
+                "variable-declaration": "nospace"
+            },
+            {
+                "call-signature": "onespace",
+                "index-signature": "onespace",
+                "parameter": "onespace",
+                "property-declaration": "onespace",
+                "variable-declaration": "onespace"
+            }
+        ],
+        "no-internal-module": true,
+        "no-trailing-whitespace": true,
+        "no-null-keyword": true,
+        "prefer-const": true,
+        "jsdoc-format": true
+    }
+}
diff --git a/themes/squares/server/types/.directory b/themes/squares/server/types/.directory
new file mode 100644
index 00000000..1e65000e
--- /dev/null
+++ b/themes/squares/server/types/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,58,27
+Version=3
+ViewMode=1
diff --git a/themes/squares/server/types/AuthenticationSession.ts b/themes/squares/server/types/AuthenticationSession.ts
new file mode 100644
index 00000000..bbed0e71
--- /dev/null
+++ b/themes/squares/server/types/AuthenticationSession.ts
@@ -0,0 +1,18 @@
+import U2f = require("u2f");
+import { Level } from "../src/lib/authentication/Level";
+
+export interface AuthenticationSession {
+  userid: string;
+  authentication_level: Level;
+  keep_me_logged_in: boolean;
+  last_activity_datetime: number;
+  identity_check?: {
+    challenge: string;
+    userid: string;
+  };
+  register_request?: U2f.Request;
+  sign_request?: U2f.Request;
+  email: string;
+  groups: string[];
+  redirect?: string;
+}
\ No newline at end of file
diff --git a/themes/squares/server/types/Dependencies.ts b/themes/squares/server/types/Dependencies.ts
new file mode 100644
index 00000000..f20404db
--- /dev/null
+++ b/themes/squares/server/types/Dependencies.ts
@@ -0,0 +1,29 @@
+import winston = require("winston");
+import speakeasy = require("speakeasy");
+import nodemailer = require("nodemailer");
+import session = require("express-session");
+import nedb = require("nedb");
+import ldapjs = require("ldapjs");
+import u2f = require("u2f");
+import RedisSession = require("connect-redis");
+import Redis = require("redis");
+
+export type Speakeasy = typeof speakeasy;
+export type Winston = typeof winston;
+export type Session = typeof session;
+export type Nedb = typeof nedb;
+export type Ldapjs = typeof ldapjs;
+export type U2f = typeof u2f;
+export type ConnectRedis = typeof RedisSession;
+export type Redis = typeof Redis;
+
+export interface GlobalDependencies {
+    u2f: U2f;
+    ldapjs: Ldapjs;
+    session: Session;
+    Redis: Redis;
+    ConnectRedis: ConnectRedis;
+    winston: Winston;
+    speakeasy: Speakeasy;
+    nedb: Nedb;
+}
\ No newline at end of file
diff --git a/themes/squares/server/types/Identity.ts b/themes/squares/server/types/Identity.ts
new file mode 100644
index 00000000..e985984e
--- /dev/null
+++ b/themes/squares/server/types/Identity.ts
@@ -0,0 +1,6 @@
+
+
+export interface Identity {
+    userid: string;
+    email: string;
+}
\ No newline at end of file
diff --git a/themes/squares/server/types/TOTPSecret.ts b/themes/squares/server/types/TOTPSecret.ts
new file mode 100644
index 00000000..d6775f2f
--- /dev/null
+++ b/themes/squares/server/types/TOTPSecret.ts
@@ -0,0 +1,11 @@
+
+export interface TOTPSecret {
+    ascii: string;
+    hex: string;
+    base32: string;
+    qr_code_ascii: string;
+    qr_code_hex: string;
+    qr_code_base32: string;
+    google_auth_qr: string;
+    otpauth_url: string;
+  }
\ No newline at end of file
diff --git a/themes/squares/server/types/U2FRegistration.ts b/themes/squares/server/types/U2FRegistration.ts
new file mode 100644
index 00000000..b6080af0
--- /dev/null
+++ b/themes/squares/server/types/U2FRegistration.ts
@@ -0,0 +1,5 @@
+
+export interface U2FRegistration {
+  keyHandle: string;
+  publicKey: string;
+}
\ No newline at end of file
diff --git a/themes/squares/server/types/dovehash.d.ts b/themes/squares/server/types/dovehash.d.ts
new file mode 100644
index 00000000..c354609c
--- /dev/null
+++ b/themes/squares/server/types/dovehash.d.ts
@@ -0,0 +1,4 @@
+
+declare module "dovehash" {
+    function encode(algo: string, text: string): string;
+}
\ No newline at end of file
diff --git a/themes/squares/server/types/speakeasy.d.ts b/themes/squares/server/types/speakeasy.d.ts
new file mode 100644
index 00000000..6ea06948
--- /dev/null
+++ b/themes/squares/server/types/speakeasy.d.ts
@@ -0,0 +1,96 @@
+declare module "speakeasy" {
+  export = speakeasy
+
+  interface SharedOptions {
+    encoding?: string
+    algorithm?: string
+  }
+
+  interface DigestOptions extends SharedOptions {
+    secret: string
+    counter: number
+  }
+
+  interface HOTPOptions extends SharedOptions {
+    secret: string
+    counter: number
+    digest?: Buffer
+    digits?: number
+  }
+
+  interface HOTPVerifyOptions extends SharedOptions {
+    secret: string
+    token: string
+    counter: number
+    digits?: number
+    window?: number
+  }
+
+  interface TOTPOptions extends SharedOptions {
+    secret: string
+    time?: number
+    step?: number
+    epoch?: number
+    counter?: number
+    digits?: number
+  }
+
+  interface TOTPVerifyOptions extends SharedOptions {
+    secret: string
+    token: string
+    time?: number
+    step?: number
+    epoch?: number
+    counter?: number
+    digits?: number
+    window?: number
+  }
+
+  interface GenerateSecretOptions {
+    length?: number
+    symbols?: boolean
+    otpauth_url?: boolean
+    name?: string
+    issuer?: string
+  }
+
+  interface GeneratedSecret {
+    ascii: string
+    hex: string
+    base32: string
+    qr_code_ascii: string
+    qr_code_hex: string
+    qr_code_base32: string
+    google_auth_qr: string
+    otpauth_url: string
+  }
+
+  interface OTPAuthURLOptions extends SharedOptions {
+    secret: string
+    label: string
+    type?: string
+    counter?: number
+    issuer?: string
+    digits?: number
+    period?: number
+  }
+
+  interface Speakeasy {
+    digest: (options: DigestOptions) => Buffer
+    hotp: {
+      (options: HOTPOptions): string,
+      verifyDelta: (options: HOTPVerifyOptions) => boolean,
+      verify: (options: HOTPVerifyOptions) => boolean,
+    }
+    totp: {
+      (options: TOTPOptions): string
+      verifyDelta: (options: TOTPVerifyOptions) => boolean,
+      verify: (options: TOTPVerifyOptions) => boolean,
+    }
+    generateSecret: (options?: GenerateSecretOptions) => GeneratedSecret
+    generateSecretASCII: (length?: number, symbols?: boolean) => string
+    otpauthURL: (options: OTPAuthURLOptions) => string
+  }
+
+  const speakeasy: Speakeasy
+}
\ No newline at end of file
diff --git a/themes/triangles/client/src/.directory b/themes/triangles/client/src/.directory
new file mode 100644
index 00000000..72a940d6
--- /dev/null
+++ b/themes/triangles/client/src/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,18,8,25,40
+Version=3
+ViewMode=1
diff --git a/themes/triangles/client/src/css/.directory b/themes/triangles/client/src/css/.directory
new file mode 100644
index 00000000..6e4b3f63
--- /dev/null
+++ b/themes/triangles/client/src/css/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,56,41
+Version=3
+ViewMode=1
diff --git a/themes/triangles/client/src/css/00-bootstrap.min.css b/themes/triangles/client/src/css/00-bootstrap.min.css
new file mode 100644
index 00000000..dfeacbb8
--- /dev/null
+++ b/themes/triangles/client/src/css/00-bootstrap.min.css
@@ -0,0 +1,5768 @@
+/*! * Bootstrap v3.3.7 (http://getbootstrap.com) * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+html{
+    font-family:sans-serif;
+    -webkit-text-size-adjust:100%;
+    -ms-text-size-adjust:100%
+}
+body{
+    margin:0
+}
+article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{
+    display:block
+}
+audio,canvas,progress,video{
+    display:inline-block;
+    vertical-align:baseline
+}
+audio:not([controls]){
+    display:none;
+    height:0
+}
+[hidden],template{
+    display:none
+}
+a{
+    background-color:transparent
+}
+a:active,a:hover{
+    outline:0
+}
+abbr[title]{
+    border-bottom:1px dotted
+}
+b,strong{
+    font-weight:700
+}
+dfn{
+    font-style:italic
+}
+h1{
+    margin:.67em 0;
+    font-size:2em
+}
+mark{
+    color:#000;
+    background:#ff0
+}
+small{
+    font-size:80%
+}
+sub,sup{
+    position:relative;
+    font-size:75%;
+    line-height:0;
+    vertical-align:baseline
+}
+sup{
+    top:-.5em
+}
+sub{
+    bottom:-.25em
+}
+img{
+    border:0
+}
+svg:not(:root){
+    overflow:hidden
+}
+figure{
+    margin:1em 40px
+}
+hr{
+    height:0;
+    -webkit-box-sizing:content-box;
+    -moz-box-sizing:content-box;
+    box-sizing:content-box
+}
+pre{
+    overflow:auto
+}
+code,kbd,pre,samp{
+    font-family:monospace,monospace;
+    font-size:1em
+}
+button,input,optgroup,select,textarea{
+    margin:0;
+    font:inherit;
+    color:inherit
+}
+button{
+    overflow:visible
+}
+button,select{
+    text-transform:none
+}
+button,html input[type=button],input[type=reset],input[type=submit]{
+    -webkit-appearance:button;
+    cursor:pointer
+}
+button[disabled],html input[disabled]{
+    cursor:default
+}
+button::-moz-focus-inner,input::-moz-focus-inner{
+    padding:0;
+    border:0
+}
+input{
+    line-height:normal
+}
+input[type=checkbox],input[type=radio]{
+    -webkit-box-sizing:border-box;
+    -moz-box-sizing:border-box;
+    box-sizing:border-box;
+    padding:0
+}
+input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{
+    height:auto
+}
+input[type=search]{
+    -webkit-box-sizing:content-box;
+    -moz-box-sizing:content-box;
+    box-sizing:content-box;
+    -webkit-appearance:textfield
+}
+input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{
+    -webkit-appearance:none
+}
+fieldset{
+    padding:.35em .625em .75em;
+    margin:0 2px;
+    border:1px solid silver
+}
+legend{
+    padding:0;
+    border:0
+}
+textarea{
+    overflow:auto
+}
+optgroup{
+    font-weight:700
+}
+table{
+    border-spacing:0;
+    border-collapse:collapse
+}
+td,th{
+    padding:0
+}
+/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */
+@media print{
+    *,:after,:before{
+        color:#000!important;
+        text-shadow:none!important;
+        background:0 0!important;
+        -webkit-box-shadow:none!important;
+        box-shadow:none!important
+    }
+    a,a:visited{
+        text-decoration:underline
+    }
+    a[href]:after{
+        content:" (" attr(href) ")"
+    }
+    abbr[title]:after{
+        content:" (" attr(title) ")"
+    }
+    a[href^="javascript:"]:after,a[href^="#"]:after{
+        content:""
+    }
+    blockquote,pre{
+        border:1px solid #999;
+        page-break-inside:avoid
+    }
+    thead{
+        display:table-header-group
+    }
+    img,tr{
+        page-break-inside:avoid
+    }
+    img{
+        max-width:100%!important
+    }
+    h2,h3,p{
+        orphans:3;
+        widows:3
+    }
+    h2,h3{
+        page-break-after:avoid
+    }
+    .navbar{
+        display:none
+    }
+    .btn>.caret,.dropup>.btn>.caret{
+        border-top-color:#000!important
+    }
+    .label{
+        border:1px solid #000
+    }
+    .table{
+        border-collapse:collapse!important
+    }
+    .table td,.table th{
+        background-color:#fff!important
+    }
+    .table-bordered td,.table-bordered th{
+        border:1px solid #ddd!important
+    }
+}
+@font-face{
+    font-family:'Glyphicons Halflings';
+    src:url(../fonts/glyphicons-halflings-regular.eot);
+    src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')
+}
+.glyphicon{
+    position:relative;
+    top:1px;
+    display:inline-block;
+    font-family:'Glyphicons Halflings';
+    font-style:normal;
+    font-weight:400;
+    line-height:1;
+    -webkit-font-smoothing:antialiased;
+    -moz-osx-font-smoothing:grayscale
+}
+.glyphicon-asterisk:before{
+    content:"\002a"
+}
+.glyphicon-plus:before{
+    content:"\002b"
+}
+.glyphicon-eur:before,.glyphicon-euro:before{
+    content:"\20ac"
+}
+.glyphicon-minus:before{
+    content:"\2212"
+}
+.glyphicon-cloud:before{
+    content:"\2601"
+}
+.glyphicon-envelope:before{
+    content:"\2709"
+}
+.glyphicon-pencil:before{
+    content:"\270f"
+}
+.glyphicon-glass:before{
+    content:"\e001"
+}
+.glyphicon-music:before{
+    content:"\e002"
+}
+.glyphicon-search:before{
+    content:"\e003"
+}
+.glyphicon-heart:before{
+    content:"\e005"
+}
+.glyphicon-star:before{
+    content:"\e006"
+}
+.glyphicon-star-empty:before{
+    content:"\e007"
+}
+.glyphicon-user:before{
+    content:"\e008"
+}
+.glyphicon-film:before{
+    content:"\e009"
+}
+.glyphicon-th-large:before{
+    content:"\e010"
+}
+.glyphicon-th:before{
+    content:"\e011"
+}
+.glyphicon-th-list:before{
+    content:"\e012"
+}
+.glyphicon-ok:before{
+    content:"\e013"
+}
+.glyphicon-remove:before{
+    content:"\e014"
+}
+.glyphicon-zoom-in:before{
+    content:"\e015"
+}
+.glyphicon-zoom-out:before{
+    content:"\e016"
+}
+.glyphicon-off:before{
+    content:"\e017"
+}
+.glyphicon-signal:before{
+    content:"\e018"
+}
+.glyphicon-cog:before{
+    content:"\e019"
+}
+.glyphicon-trash:before{
+    content:"\e020"
+}
+.glyphicon-home:before{
+    content:"\e021"
+}
+.glyphicon-file:before{
+    content:"\e022"
+}
+.glyphicon-time:before{
+    content:"\e023"
+}
+.glyphicon-road:before{
+    content:"\e024"
+}
+.glyphicon-download-alt:before{
+    content:"\e025"
+}
+.glyphicon-download:before{
+    content:"\e026"
+}
+.glyphicon-upload:before{
+    content:"\e027"
+}
+.glyphicon-inbox:before{
+    content:"\e028"
+}
+.glyphicon-play-circle:before{
+    content:"\e029"
+}
+.glyphicon-repeat:before{
+    content:"\e030"
+}
+.glyphicon-refresh:before{
+    content:"\e031"
+}
+.glyphicon-list-alt:before{
+    content:"\e032"
+}
+.glyphicon-lock:before{
+    content:"\e033"
+}
+.glyphicon-flag:before{
+    content:"\e034"
+}
+.glyphicon-headphones:before{
+    content:"\e035"
+}
+.glyphicon-volume-off:before{
+    content:"\e036"
+}
+.glyphicon-volume-down:before{
+    content:"\e037"
+}
+.glyphicon-volume-up:before{
+    content:"\e038"
+}
+.glyphicon-qrcode:before{
+    content:"\e039"
+}
+.glyphicon-barcode:before{
+    content:"\e040"
+}
+.glyphicon-tag:before{
+    content:"\e041"
+}
+.glyphicon-tags:before{
+    content:"\e042"
+}
+.glyphicon-book:before{
+    content:"\e043"
+}
+.glyphicon-bookmark:before{
+    content:"\e044"
+}
+.glyphicon-print:before{
+    content:"\e045"
+}
+.glyphicon-camera:before{
+    content:"\e046"
+}
+.glyphicon-font:before{
+    content:"\e047"
+}
+.glyphicon-bold:before{
+    content:"\e048"
+}
+.glyphicon-italic:before{
+    content:"\e049"
+}
+.glyphicon-text-height:before{
+    content:"\e050"
+}
+.glyphicon-text-width:before{
+    content:"\e051"
+}
+.glyphicon-align-left:before{
+    content:"\e052"
+}
+.glyphicon-align-center:before{
+    content:"\e053"
+}
+.glyphicon-align-right:before{
+    content:"\e054"
+}
+.glyphicon-align-justify:before{
+    content:"\e055"
+}
+.glyphicon-list:before{
+    content:"\e056"
+}
+.glyphicon-indent-left:before{
+    content:"\e057"
+}
+.glyphicon-indent-right:before{
+    content:"\e058"
+}
+.glyphicon-facetime-video:before{
+    content:"\e059"
+}
+.glyphicon-picture:before{
+    content:"\e060"
+}
+.glyphicon-map-marker:before{
+    content:"\e062"
+}
+.glyphicon-adjust:before{
+    content:"\e063"
+}
+.glyphicon-tint:before{
+    content:"\e064"
+}
+.glyphicon-edit:before{
+    content:"\e065"
+}
+.glyphicon-share:before{
+    content:"\e066"
+}
+.glyphicon-check:before{
+    content:"\e067"
+}
+.glyphicon-move:before{
+    content:"\e068"
+}
+.glyphicon-step-backward:before{
+    content:"\e069"
+}
+.glyphicon-fast-backward:before{
+    content:"\e070"
+}
+.glyphicon-backward:before{
+    content:"\e071"
+}
+.glyphicon-play:before{
+    content:"\e072"
+}
+.glyphicon-pause:before{
+    content:"\e073"
+}
+.glyphicon-stop:before{
+    content:"\e074"
+}
+.glyphicon-forward:before{
+    content:"\e075"
+}
+.glyphicon-fast-forward:before{
+    content:"\e076"
+}
+.glyphicon-step-forward:before{
+    content:"\e077"
+}
+.glyphicon-eject:before{
+    content:"\e078"
+}
+.glyphicon-chevron-left:before{
+    content:"\e079"
+}
+.glyphicon-chevron-right:before{
+    content:"\e080"
+}
+.glyphicon-plus-sign:before{
+    content:"\e081"
+}
+.glyphicon-minus-sign:before{
+    content:"\e082"
+}
+.glyphicon-remove-sign:before{
+    content:"\e083"
+}
+.glyphicon-ok-sign:before{
+    content:"\e084"
+}
+.glyphicon-question-sign:before{
+    content:"\e085"
+}
+.glyphicon-info-sign:before{
+    content:"\e086"
+}
+.glyphicon-screenshot:before{
+    content:"\e087"
+}
+.glyphicon-remove-circle:before{
+    content:"\e088"
+}
+.glyphicon-ok-circle:before{
+    content:"\e089"
+}
+.glyphicon-ban-circle:before{
+    content:"\e090"
+}
+.glyphicon-arrow-left:before{
+    content:"\e091"
+}
+.glyphicon-arrow-right:before{
+    content:"\e092"
+}
+.glyphicon-arrow-up:before{
+    content:"\e093"
+}
+.glyphicon-arrow-down:before{
+    content:"\e094"
+}
+.glyphicon-share-alt:before{
+    content:"\e095"
+}
+.glyphicon-resize-full:before{
+    content:"\e096"
+}
+.glyphicon-resize-small:before{
+    content:"\e097"
+}
+.glyphicon-exclamation-sign:before{
+    content:"\e101"
+}
+.glyphicon-gift:before{
+    content:"\e102"
+}
+.glyphicon-leaf:before{
+    content:"\e103"
+}
+.glyphicon-fire:before{
+    content:"\e104"
+}
+.glyphicon-eye-open:before{
+    content:"\e105"
+}
+.glyphicon-eye-close:before{
+    content:"\e106"
+}
+.glyphicon-warning-sign:before{
+    content:"\e107"
+}
+.glyphicon-plane:before{
+    content:"\e108"
+}
+.glyphicon-calendar:before{
+    content:"\e109"
+}
+.glyphicon-random:before{
+    content:"\e110"
+}
+.glyphicon-comment:before{
+    content:"\e111"
+}
+.glyphicon-magnet:before{
+    content:"\e112"
+}
+.glyphicon-chevron-up:before{
+    content:"\e113"
+}
+.glyphicon-chevron-down:before{
+    content:"\e114"
+}
+.glyphicon-retweet:before{
+    content:"\e115"
+}
+.glyphicon-shopping-cart:before{
+    content:"\e116"
+}
+.glyphicon-folder-close:before{
+    content:"\e117"
+}
+.glyphicon-folder-open:before{
+    content:"\e118"
+}
+.glyphicon-resize-vertical:before{
+    content:"\e119"
+}
+.glyphicon-resize-horizontal:before{
+    content:"\e120"
+}
+.glyphicon-hdd:before{
+    content:"\e121"
+}
+.glyphicon-bullhorn:before{
+    content:"\e122"
+}
+.glyphicon-bell:before{
+    content:"\e123"
+}
+.glyphicon-certificate:before{
+    content:"\e124"
+}
+.glyphicon-thumbs-up:before{
+    content:"\e125"
+}
+.glyphicon-thumbs-down:before{
+    content:"\e126"
+}
+.glyphicon-hand-right:before{
+    content:"\e127"
+}
+.glyphicon-hand-left:before{
+    content:"\e128"
+}
+.glyphicon-hand-up:before{
+    content:"\e129"
+}
+.glyphicon-hand-down:before{
+    content:"\e130"
+}
+.glyphicon-circle-arrow-right:before{
+    content:"\e131"
+}
+.glyphicon-circle-arrow-left:before{
+    content:"\e132"
+}
+.glyphicon-circle-arrow-up:before{
+    content:"\e133"
+}
+.glyphicon-circle-arrow-down:before{
+    content:"\e134"
+}
+.glyphicon-globe:before{
+    content:"\e135"
+}
+.glyphicon-wrench:before{
+    content:"\e136"
+}
+.glyphicon-tasks:before{
+    content:"\e137"
+}
+.glyphicon-filter:before{
+    content:"\e138"
+}
+.glyphicon-briefcase:before{
+    content:"\e139"
+}
+.glyphicon-fullscreen:before{
+    content:"\e140"
+}
+.glyphicon-dashboard:before{
+    content:"\e141"
+}
+.glyphicon-paperclip:before{
+    content:"\e142"
+}
+.glyphicon-heart-empty:before{
+    content:"\e143"
+}
+.glyphicon-link:before{
+    content:"\e144"
+}
+.glyphicon-phone:before{
+    content:"\e145"
+}
+.glyphicon-pushpin:before{
+    content:"\e146"
+}
+.glyphicon-usd:before{
+    content:"\e148"
+}
+.glyphicon-gbp:before{
+    content:"\e149"
+}
+.glyphicon-sort:before{
+    content:"\e150"
+}
+.glyphicon-sort-by-alphabet:before{
+    content:"\e151"
+}
+.glyphicon-sort-by-alphabet-alt:before{
+    content:"\e152"
+}
+.glyphicon-sort-by-order:before{
+    content:"\e153"
+}
+.glyphicon-sort-by-order-alt:before{
+    content:"\e154"
+}
+.glyphicon-sort-by-attributes:before{
+    content:"\e155"
+}
+.glyphicon-sort-by-attributes-alt:before{
+    content:"\e156"
+}
+.glyphicon-unchecked:before{
+    content:"\e157"
+}
+.glyphicon-expand:before{
+    content:"\e158"
+}
+.glyphicon-collapse-down:before{
+    content:"\e159"
+}
+.glyphicon-collapse-up:before{
+    content:"\e160"
+}
+.glyphicon-log-in:before{
+    content:"\e161"
+}
+.glyphicon-flash:before{
+    content:"\e162"
+}
+.glyphicon-log-out:before{
+    content:"\e163"
+}
+.glyphicon-new-window:before{
+    content:"\e164"
+}
+.glyphicon-record:before{
+    content:"\e165"
+}
+.glyphicon-save:before{
+    content:"\e166"
+}
+.glyphicon-open:before{
+    content:"\e167"
+}
+.glyphicon-saved:before{
+    content:"\e168"
+}
+.glyphicon-import:before{
+    content:"\e169"
+}
+.glyphicon-export:before{
+    content:"\e170"
+}
+.glyphicon-send:before{
+    content:"\e171"
+}
+.glyphicon-floppy-disk:before{
+    content:"\e172"
+}
+.glyphicon-floppy-saved:before{
+    content:"\e173"
+}
+.glyphicon-floppy-remove:before{
+    content:"\e174"
+}
+.glyphicon-floppy-save:before{
+    content:"\e175"
+}
+.glyphicon-floppy-open:before{
+    content:"\e176"
+}
+.glyphicon-credit-card:before{
+    content:"\e177"
+}
+.glyphicon-transfer:before{
+    content:"\e178"
+}
+.glyphicon-cutlery:before{
+    content:"\e179"
+}
+.glyphicon-header:before{
+    content:"\e180"
+}
+.glyphicon-compressed:before{
+    content:"\e181"
+}
+.glyphicon-earphone:before{
+    content:"\e182"
+}
+.glyphicon-phone-alt:before{
+    content:"\e183"
+}
+.glyphicon-tower:before{
+    content:"\e184"
+}
+.glyphicon-stats:before{
+    content:"\e185"
+}
+.glyphicon-sd-video:before{
+    content:"\e186"
+}
+.glyphicon-hd-video:before{
+    content:"\e187"
+}
+.glyphicon-subtitles:before{
+    content:"\e188"
+}
+.glyphicon-sound-stereo:before{
+    content:"\e189"
+}
+.glyphicon-sound-dolby:before{
+    content:"\e190"
+}
+.glyphicon-sound-5-1:before{
+    content:"\e191"
+}
+.glyphicon-sound-6-1:before{
+    content:"\e192"
+}
+.glyphicon-sound-7-1:before{
+    content:"\e193"
+}
+.glyphicon-copyright-mark:before{
+    content:"\e194"
+}
+.glyphicon-registration-mark:before{
+    content:"\e195"
+}
+.glyphicon-cloud-download:before{
+    content:"\e197"
+}
+.glyphicon-cloud-upload:before{
+    content:"\e198"
+}
+.glyphicon-tree-conifer:before{
+    content:"\e199"
+}
+.glyphicon-tree-deciduous:before{
+    content:"\e200"
+}
+.glyphicon-cd:before{
+    content:"\e201"
+}
+.glyphicon-save-file:before{
+    content:"\e202"
+}
+.glyphicon-open-file:before{
+    content:"\e203"
+}
+.glyphicon-level-up:before{
+    content:"\e204"
+}
+.glyphicon-copy:before{
+    content:"\e205"
+}
+.glyphicon-paste:before{
+    content:"\e206"
+}
+.glyphicon-alert:before{
+    content:"\e209"
+}
+.glyphicon-equalizer:before{
+    content:"\e210"
+}
+.glyphicon-king:before{
+    content:"\e211"
+}
+.glyphicon-queen:before{
+    content:"\e212"
+}
+.glyphicon-pawn:before{
+    content:"\e213"
+}
+.glyphicon-bishop:before{
+    content:"\e214"
+}
+.glyphicon-knight:before{
+    content:"\e215"
+}
+.glyphicon-baby-formula:before{
+    content:"\e216"
+}
+.glyphicon-tent:before{
+    content:"\26fa"
+}
+.glyphicon-blackboard:before{
+    content:"\e218"
+}
+.glyphicon-bed:before{
+    content:"\e219"
+}
+.glyphicon-apple:before{
+    content:"\f8ff"
+}
+.glyphicon-erase:before{
+    content:"\e221"
+}
+.glyphicon-hourglass:before{
+    content:"\231b"
+}
+.glyphicon-lamp:before{
+    content:"\e223"
+}
+.glyphicon-duplicate:before{
+    content:"\e224"
+}
+.glyphicon-piggy-bank:before{
+    content:"\e225"
+}
+.glyphicon-scissors:before{
+    content:"\e226"
+}
+.glyphicon-bitcoin:before{
+    content:"\e227"
+}
+.glyphicon-btc:before{
+    content:"\e227"
+}
+.glyphicon-xbt:before{
+    content:"\e227"
+}
+.glyphicon-yen:before{
+    content:"\00a5"
+}
+.glyphicon-jpy:before{
+    content:"\00a5"
+}
+.glyphicon-ruble:before{
+    content:"\20bd"
+}
+.glyphicon-rub:before{
+    content:"\20bd"
+}
+.glyphicon-scale:before{
+    content:"\e230"
+}
+.glyphicon-ice-lolly:before{
+    content:"\e231"
+}
+.glyphicon-ice-lolly-tasted:before{
+    content:"\e232"
+}
+.glyphicon-education:before{
+    content:"\e233"
+}
+.glyphicon-option-horizontal:before{
+    content:"\e234"
+}
+.glyphicon-option-vertical:before{
+    content:"\e235"
+}
+.glyphicon-menu-hamburger:before{
+    content:"\e236"
+}
+.glyphicon-modal-window:before{
+    content:"\e237"
+}
+.glyphicon-oil:before{
+    content:"\e238"
+}
+.glyphicon-grain:before{
+    content:"\e239"
+}
+.glyphicon-sunglasses:before{
+    content:"\e240"
+}
+.glyphicon-text-size:before{
+    content:"\e241"
+}
+.glyphicon-text-color:before{
+    content:"\e242"
+}
+.glyphicon-text-background:before{
+    content:"\e243"
+}
+.glyphicon-object-align-top:before{
+    content:"\e244"
+}
+.glyphicon-object-align-bottom:before{
+    content:"\e245"
+}
+.glyphicon-object-align-horizontal:before{
+    content:"\e246"
+}
+.glyphicon-object-align-left:before{
+    content:"\e247"
+}
+.glyphicon-object-align-vertical:before{
+    content:"\e248"
+}
+.glyphicon-object-align-right:before{
+    content:"\e249"
+}
+.glyphicon-triangle-right:before{
+    content:"\e250"
+}
+.glyphicon-triangle-left:before{
+    content:"\e251"
+}
+.glyphicon-triangle-bottom:before{
+    content:"\e252"
+}
+.glyphicon-triangle-top:before{
+    content:"\e253"
+}
+.glyphicon-console:before{
+    content:"\e254"
+}
+.glyphicon-superscript:before{
+    content:"\e255"
+}
+.glyphicon-subscript:before{
+    content:"\e256"
+}
+.glyphicon-menu-left:before{
+    content:"\e257"
+}
+.glyphicon-menu-right:before{
+    content:"\e258"
+}
+.glyphicon-menu-down:before{
+    content:"\e259"
+}
+.glyphicon-menu-up:before{
+    content:"\e260"
+}
+*{
+    -webkit-box-sizing:border-box;
+    -moz-box-sizing:border-box;
+    box-sizing:border-box
+}
+:after,:before{
+    -webkit-box-sizing:border-box;
+    -moz-box-sizing:border-box;
+    box-sizing:border-box
+}
+html{
+    font-size:10px;
+    -webkit-tap-highlight-color:rgba(0,0,0,0)
+}
+body{
+    font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
+    font-size:14px;
+    line-height:1.42857143;
+    color:#333;
+    background-color:#fff
+}
+button,input,select,textarea{
+    font-family:inherit;
+    font-size:inherit;
+    line-height:inherit
+}
+a{
+    color:#337ab7;
+    text-decoration:none
+}
+a:focus,a:hover{
+    color:#23527c;
+    text-decoration:underline
+}
+a:focus{
+    outline:5px auto -webkit-focus-ring-color;
+    outline-offset:-2px
+}
+figure{
+    margin:0
+}
+img{
+    vertical-align:middle
+}
+.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{
+    display:block;
+    max-width:100%;
+    height:auto
+}
+.img-rounded{
+    border-radius:6px
+}
+.img-thumbnail{
+    display:inline-block;
+    max-width:100%;
+    height:auto;
+    padding:4px;
+    line-height:1.42857143;
+    background-color:#fff;
+    border:1px solid #ddd;
+    border-radius:4px;
+    -webkit-transition:all .2s ease-in-out;
+    -o-transition:all .2s ease-in-out;
+    transition:all .2s ease-in-out
+}
+.img-circle{
+    border-radius:50%
+}
+hr{
+    margin-top:20px;
+    margin-bottom:20px;
+    border:0;
+    border-top:1px solid #eee
+}
+.sr-only{
+    position:absolute;
+    width:1px;
+    height:1px;
+    padding:0;
+    margin:-1px;
+    overflow:hidden;
+    clip:rect(0,0,0,0);
+    border:0
+}
+.sr-only-focusable:active,.sr-only-focusable:focus{
+    position:static;
+    width:auto;
+    height:auto;
+    margin:0;
+    overflow:visible;
+    clip:auto
+}
+[role=button]{
+    cursor:pointer
+}
+.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{
+    font-family:inherit;
+    font-weight:500;
+    line-height:1.1;
+    color:inherit
+}
+.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{
+    font-weight:400;
+    line-height:1;
+    color:#777
+}
+.h1,.h2,.h3,h1,h2,h3{
+    margin-top:20px;
+    margin-bottom:10px
+}
+.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{
+    font-size:65%
+}
+.h4,.h5,.h6,h4,h5,h6{
+    margin-top:10px;
+    margin-bottom:10px
+}
+.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{
+    font-size:75%
+}
+.h1,h1{
+    font-size:36px
+}
+.h2,h2{
+    font-size:30px
+}
+.h3,h3{
+    font-size:24px
+}
+.h4,h4{
+    font-size:18px
+}
+.h5,h5{
+    font-size:14px
+}
+.h6,h6{
+    font-size:12px
+}
+p{
+    margin:0 0 10px
+}
+.lead{
+    margin-bottom:20px;
+    font-size:16px;
+    font-weight:300;
+    line-height:1.4
+}
+@media (min-width:768px){
+    .lead{
+        font-size:21px
+    }
+}
+.small,small{
+    font-size:85%
+}
+.mark,mark{
+    padding:.2em;
+    background-color:#fcf8e3
+}
+.text-left{
+    text-align:left
+}
+.text-right{
+    text-align:right
+}
+.text-center{
+    text-align:center
+}
+.text-justify{
+    text-align:justify
+}
+.text-nowrap{
+    white-space:nowrap
+}
+.text-lowercase{
+    text-transform:lowercase
+}
+.text-uppercase{
+    text-transform:uppercase
+}
+.text-capitalize{
+    text-transform:capitalize
+}
+.text-muted{
+    color:#777
+}
+.text-primary{
+    color:#337ab7
+}
+a.text-primary:focus,a.text-primary:hover{
+    color:#286090
+}
+.text-success{
+    color:#3c763d
+}
+a.text-success:focus,a.text-success:hover{
+    color:#2b542c
+}
+.text-info{
+    color:#31708f
+}
+a.text-info:focus,a.text-info:hover{
+    color:#245269
+}
+.text-warning{
+    color:#8a6d3b
+}
+a.text-warning:focus,a.text-warning:hover{
+    color:#66512c
+}
+.text-danger{
+    color:#a94442
+}
+a.text-danger:focus,a.text-danger:hover{
+    color:#843534
+}
+.bg-primary{
+    color:#fff;
+    background-color:#337ab7
+}
+a.bg-primary:focus,a.bg-primary:hover{
+    background-color:#286090
+}
+.bg-success{
+    background-color:#dff0d8
+}
+a.bg-success:focus,a.bg-success:hover{
+    background-color:#c1e2b3
+}
+.bg-info{
+    background-color:#d9edf7
+}
+a.bg-info:focus,a.bg-info:hover{
+    background-color:#afd9ee
+}
+.bg-warning{
+    background-color:#fcf8e3
+}
+a.bg-warning:focus,a.bg-warning:hover{
+    background-color:#f7ecb5
+}
+.bg-danger{
+    background-color:#f2dede
+}
+a.bg-danger:focus,a.bg-danger:hover{
+    background-color:#e4b9b9
+}
+.page-header{
+    padding-bottom:9px;
+    margin:40px 0 20px;
+    border-bottom:1px solid #eee
+}
+ol,ul{
+    margin-top:0;
+    margin-bottom:10px
+}
+ol ol,ol ul,ul ol,ul ul{
+    margin-bottom:0
+}
+.list-unstyled{
+    padding-left:0;
+    list-style:none
+}
+.list-inline{
+    padding-left:0;
+    margin-left:-5px;
+    list-style:none
+}
+.list-inline>li{
+    display:inline-block;
+    padding-right:5px;
+    padding-left:5px
+}
+dl{
+    margin-top:0;
+    margin-bottom:20px
+}
+dd,dt{
+    line-height:1.42857143
+}
+dt{
+    font-weight:700
+}
+dd{
+    margin-left:0
+}
+@media (min-width:768px){
+    .dl-horizontal dt{
+        float:left;
+        width:160px;
+        overflow:hidden;
+        clear:left;
+        text-align:right;
+        text-overflow:ellipsis;
+        white-space:nowrap
+    }
+    .dl-horizontal dd{
+        margin-left:180px
+    }
+}
+abbr[data-original-title],abbr[title]{
+    cursor:help;
+    border-bottom:1px dotted #777
+}
+.initialism{
+    font-size:90%;
+    text-transform:uppercase
+}
+blockquote{
+    padding:10px 20px;
+    margin:0 0 20px;
+    font-size:17.5px;
+    border-left:5px solid #eee
+}
+blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{
+    margin-bottom:0
+}
+blockquote .small,blockquote footer,blockquote small{
+    display:block;
+    font-size:80%;
+    line-height:1.42857143;
+    color:#777
+}
+blockquote .small:before,blockquote footer:before,blockquote small:before{
+    content:'\2014 \00A0'
+}
+.blockquote-reverse,blockquote.pull-right{
+    padding-right:15px;
+    padding-left:0;
+    text-align:right;
+    border-right:5px solid #eee;
+    border-left:0
+}
+.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{
+    content:''
+}
+.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{
+    content:'\00A0 \2014'
+}
+address{
+    margin-bottom:20px;
+    font-style:normal;
+    line-height:1.42857143
+}
+code,kbd,pre,samp{
+    font-family:Menlo,Monaco,Consolas,"Courier New",monospace
+}
+code{
+    padding:2px 4px;
+    font-size:90%;
+    color:#c7254e;
+    background-color:#f9f2f4;
+    border-radius:4px
+}
+kbd{
+    padding:2px 4px;
+    font-size:90%;
+    color:#fff;
+    background-color:#333;
+    border-radius:3px;
+    -webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);
+    box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)
+}
+kbd kbd{
+    padding:0;
+    font-size:100%;
+    font-weight:700;
+    -webkit-box-shadow:none;
+    box-shadow:none
+}
+pre{
+    display:block;
+    padding:9.5px;
+    margin:0 0 10px;
+    font-size:13px;
+    line-height:1.42857143;
+    color:#333;
+    word-break:break-all;
+    word-wrap:break-word;
+    background-color:#f5f5f5;
+    border:1px solid #ccc;
+    border-radius:4px
+}
+pre code{
+    padding:0;
+    font-size:inherit;
+    color:inherit;
+    white-space:pre-wrap;
+    background-color:transparent;
+    border-radius:0
+}
+.pre-scrollable{
+    max-height:340px;
+    overflow-y:scroll
+}
+.container{
+    padding-right:15px;
+    padding-left:15px;
+    margin-right:auto;
+    margin-left:auto
+}
+@media (min-width:768px){
+    .container{
+        width:750px
+    }
+}
+@media (min-width:992px){
+    .container{
+        width:970px
+    }
+}
+@media (min-width:1200px){
+    .container{
+        width:1170px
+    }
+}
+.container-fluid{
+    padding-right:15px;
+    padding-left:15px;
+    margin-right:auto;
+    margin-left:auto
+}
+.row{
+    margin-right:-15px;
+    margin-left:-15px
+}
+.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{
+    position:relative;
+    min-height:1px;
+    padding-right:15px;
+    padding-left:15px
+}
+.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{
+    float:left
+}
+.col-xs-12{
+    width:100%
+}
+.col-xs-11{
+    width:91.66666667%
+}
+.col-xs-10{
+    width:83.33333333%
+}
+.col-xs-9{
+    width:75%
+}
+.col-xs-8{
+    width:66.66666667%
+}
+.col-xs-7{
+    width:58.33333333%
+}
+.col-xs-6{
+    width:50%
+}
+.col-xs-5{
+    width:41.66666667%
+}
+.col-xs-4{
+    width:33.33333333%
+}
+.col-xs-3{
+    width:25%
+}
+.col-xs-2{
+    width:16.66666667%
+}
+.col-xs-1{
+    width:8.33333333%
+}
+.col-xs-pull-12{
+    right:100%
+}
+.col-xs-pull-11{
+    right:91.66666667%
+}
+.col-xs-pull-10{
+    right:83.33333333%
+}
+.col-xs-pull-9{
+    right:75%
+}
+.col-xs-pull-8{
+    right:66.66666667%
+}
+.col-xs-pull-7{
+    right:58.33333333%
+}
+.col-xs-pull-6{
+    right:50%
+}
+.col-xs-pull-5{
+    right:41.66666667%
+}
+.col-xs-pull-4{
+    right:33.33333333%
+}
+.col-xs-pull-3{
+    right:25%
+}
+.col-xs-pull-2{
+    right:16.66666667%
+}
+.col-xs-pull-1{
+    right:8.33333333%
+}
+.col-xs-pull-0{
+    right:auto
+}
+.col-xs-push-12{
+    left:100%
+}
+.col-xs-push-11{
+    left:91.66666667%
+}
+.col-xs-push-10{
+    left:83.33333333%
+}
+.col-xs-push-9{
+    left:75%
+}
+.col-xs-push-8{
+    left:66.66666667%
+}
+.col-xs-push-7{
+    left:58.33333333%
+}
+.col-xs-push-6{
+    left:50%
+}
+.col-xs-push-5{
+    left:41.66666667%
+}
+.col-xs-push-4{
+    left:33.33333333%
+}
+.col-xs-push-3{
+    left:25%
+}
+.col-xs-push-2{
+    left:16.66666667%
+}
+.col-xs-push-1{
+    left:8.33333333%
+}
+.col-xs-push-0{
+    left:auto
+}
+.col-xs-offset-12{
+    margin-left:100%
+}
+.col-xs-offset-11{
+    margin-left:91.66666667%
+}
+.col-xs-offset-10{
+    margin-left:83.33333333%
+}
+.col-xs-offset-9{
+    margin-left:75%
+}
+.col-xs-offset-8{
+    margin-left:66.66666667%
+}
+.col-xs-offset-7{
+    margin-left:58.33333333%
+}
+.col-xs-offset-6{
+    margin-left:50%
+}
+.col-xs-offset-5{
+    margin-left:41.66666667%
+}
+.col-xs-offset-4{
+    margin-left:33.33333333%
+}
+.col-xs-offset-3{
+    margin-left:25%
+}
+.col-xs-offset-2{
+    margin-left:16.66666667%
+}
+.col-xs-offset-1{
+    margin-left:8.33333333%
+}
+.col-xs-offset-0{
+    margin-left:0
+}
+@media (min-width:768px){
+    .col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{
+        float:left
+    }
+    .col-sm-12{
+        width:100%
+    }
+    .col-sm-11{
+        width:91.66666667%
+    }
+    .col-sm-10{
+        width:83.33333333%
+    }
+    .col-sm-9{
+        width:75%
+    }
+    .col-sm-8{
+        width:66.66666667%
+    }
+    .col-sm-7{
+        width:58.33333333%
+    }
+    .col-sm-6{
+        width:50%
+    }
+    .col-sm-5{
+        width:41.66666667%
+    }
+    .col-sm-4{
+        width:33.33333333%
+    }
+    .col-sm-3{
+        width:25%
+    }
+    .col-sm-2{
+        width:16.66666667%
+    }
+    .col-sm-1{
+        width:8.33333333%
+    }
+    .col-sm-pull-12{
+        right:100%
+    }
+    .col-sm-pull-11{
+        right:91.66666667%
+    }
+    .col-sm-pull-10{
+        right:83.33333333%
+    }
+    .col-sm-pull-9{
+        right:75%
+    }
+    .col-sm-pull-8{
+        right:66.66666667%
+    }
+    .col-sm-pull-7{
+        right:58.33333333%
+    }
+    .col-sm-pull-6{
+        right:50%
+    }
+    .col-sm-pull-5{
+        right:41.66666667%
+    }
+    .col-sm-pull-4{
+        right:33.33333333%
+    }
+    .col-sm-pull-3{
+        right:25%
+    }
+    .col-sm-pull-2{
+        right:16.66666667%
+    }
+    .col-sm-pull-1{
+        right:8.33333333%
+    }
+    .col-sm-pull-0{
+        right:auto
+    }
+    .col-sm-push-12{
+        left:100%
+    }
+    .col-sm-push-11{
+        left:91.66666667%
+    }
+    .col-sm-push-10{
+        left:83.33333333%
+    }
+    .col-sm-push-9{
+        left:75%
+    }
+    .col-sm-push-8{
+        left:66.66666667%
+    }
+    .col-sm-push-7{
+        left:58.33333333%
+    }
+    .col-sm-push-6{
+        left:50%
+    }
+    .col-sm-push-5{
+        left:41.66666667%
+    }
+    .col-sm-push-4{
+        left:33.33333333%
+    }
+    .col-sm-push-3{
+        left:25%
+    }
+    .col-sm-push-2{
+        left:16.66666667%
+    }
+    .col-sm-push-1{
+        left:8.33333333%
+    }
+    .col-sm-push-0{
+        left:auto
+    }
+    .col-sm-offset-12{
+        margin-left:100%
+    }
+    .col-sm-offset-11{
+        margin-left:91.66666667%
+    }
+    .col-sm-offset-10{
+        margin-left:83.33333333%
+    }
+    .col-sm-offset-9{
+        margin-left:75%
+    }
+    .col-sm-offset-8{
+        margin-left:66.66666667%
+    }
+    .col-sm-offset-7{
+        margin-left:58.33333333%
+    }
+    .col-sm-offset-6{
+        margin-left:50%
+    }
+    .col-sm-offset-5{
+        margin-left:41.66666667%
+    }
+    .col-sm-offset-4{
+        margin-left:33.33333333%
+    }
+    .col-sm-offset-3{
+        margin-left:25%
+    }
+    .col-sm-offset-2{
+        margin-left:16.66666667%
+    }
+    .col-sm-offset-1{
+        margin-left:8.33333333%
+    }
+    .col-sm-offset-0{
+        margin-left:0
+    }
+}
+@media (min-width:992px){
+    .col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{
+        float:left
+    }
+    .col-md-12{
+        width:100%
+    }
+    .col-md-11{
+        width:91.66666667%
+    }
+    .col-md-10{
+        width:83.33333333%
+    }
+    .col-md-9{
+        width:75%
+    }
+    .col-md-8{
+        width:66.66666667%
+    }
+    .col-md-7{
+        width:58.33333333%
+    }
+    .col-md-6{
+        width:50%
+    }
+    .col-md-5{
+        width:41.66666667%
+    }
+    .col-md-4{
+        width:33.33333333%
+    }
+    .col-md-3{
+        width:25%
+    }
+    .col-md-2{
+        width:16.66666667%
+    }
+    .col-md-1{
+        width:8.33333333%
+    }
+    .col-md-pull-12{
+        right:100%
+    }
+    .col-md-pull-11{
+        right:91.66666667%
+    }
+    .col-md-pull-10{
+        right:83.33333333%
+    }
+    .col-md-pull-9{
+        right:75%
+    }
+    .col-md-pull-8{
+        right:66.66666667%
+    }
+    .col-md-pull-7{
+        right:58.33333333%
+    }
+    .col-md-pull-6{
+        right:50%
+    }
+    .col-md-pull-5{
+        right:41.66666667%
+    }
+    .col-md-pull-4{
+        right:33.33333333%
+    }
+    .col-md-pull-3{
+        right:25%
+    }
+    .col-md-pull-2{
+        right:16.66666667%
+    }
+    .col-md-pull-1{
+        right:8.33333333%
+    }
+    .col-md-pull-0{
+        right:auto
+    }
+    .col-md-push-12{
+        left:100%
+    }
+    .col-md-push-11{
+        left:91.66666667%
+    }
+    .col-md-push-10{
+        left:83.33333333%
+    }
+    .col-md-push-9{
+        left:75%
+    }
+    .col-md-push-8{
+        left:66.66666667%
+    }
+    .col-md-push-7{
+        left:58.33333333%
+    }
+    .col-md-push-6{
+        left:50%
+    }
+    .col-md-push-5{
+        left:41.66666667%
+    }
+    .col-md-push-4{
+        left:33.33333333%
+    }
+    .col-md-push-3{
+        left:25%
+    }
+    .col-md-push-2{
+        left:16.66666667%
+    }
+    .col-md-push-1{
+        left:8.33333333%
+    }
+    .col-md-push-0{
+        left:auto
+    }
+    .col-md-offset-12{
+        margin-left:100%
+    }
+    .col-md-offset-11{
+        margin-left:91.66666667%
+    }
+    .col-md-offset-10{
+        margin-left:83.33333333%
+    }
+    .col-md-offset-9{
+        margin-left:75%
+    }
+    .col-md-offset-8{
+        margin-left:66.66666667%
+    }
+    .col-md-offset-7{
+        margin-left:58.33333333%
+    }
+    .col-md-offset-6{
+        margin-left:50%
+    }
+    .col-md-offset-5{
+        margin-left:41.66666667%
+    }
+    .col-md-offset-4{
+        margin-left:33.33333333%
+    }
+    .col-md-offset-3{
+        margin-left:25%
+    }
+    .col-md-offset-2{
+        margin-left:16.66666667%
+    }
+    .col-md-offset-1{
+        margin-left:8.33333333%
+    }
+    .col-md-offset-0{
+        margin-left:0
+    }
+}
+@media (min-width:1200px){
+    .col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{
+        float:left
+    }
+    .col-lg-12{
+        width:100%
+    }
+    .col-lg-11{
+        width:91.66666667%
+    }
+    .col-lg-10{
+        width:83.33333333%
+    }
+    .col-lg-9{
+        width:75%
+    }
+    .col-lg-8{
+        width:66.66666667%
+    }
+    .col-lg-7{
+        width:58.33333333%
+    }
+    .col-lg-6{
+        width:50%
+    }
+    .col-lg-5{
+        width:41.66666667%
+    }
+    .col-lg-4{
+        width:33.33333333%
+    }
+    .col-lg-3{
+        width:25%
+    }
+    .col-lg-2{
+        width:16.66666667%
+    }
+    .col-lg-1{
+        width:8.33333333%
+    }
+    .col-lg-pull-12{
+        right:100%
+    }
+    .col-lg-pull-11{
+        right:91.66666667%
+    }
+    .col-lg-pull-10{
+        right:83.33333333%
+    }
+    .col-lg-pull-9{
+        right:75%
+    }
+    .col-lg-pull-8{
+        right:66.66666667%
+    }
+    .col-lg-pull-7{
+        right:58.33333333%
+    }
+    .col-lg-pull-6{
+        right:50%
+    }
+    .col-lg-pull-5{
+        right:41.66666667%
+    }
+    .col-lg-pull-4{
+        right:33.33333333%
+    }
+    .col-lg-pull-3{
+        right:25%
+    }
+    .col-lg-pull-2{
+        right:16.66666667%
+    }
+    .col-lg-pull-1{
+        right:8.33333333%
+    }
+    .col-lg-pull-0{
+        right:auto
+    }
+    .col-lg-push-12{
+        left:100%
+    }
+    .col-lg-push-11{
+        left:91.66666667%
+    }
+    .col-lg-push-10{
+        left:83.33333333%
+    }
+    .col-lg-push-9{
+        left:75%
+    }
+    .col-lg-push-8{
+        left:66.66666667%
+    }
+    .col-lg-push-7{
+        left:58.33333333%
+    }
+    .col-lg-push-6{
+        left:50%
+    }
+    .col-lg-push-5{
+        left:41.66666667%
+    }
+    .col-lg-push-4{
+        left:33.33333333%
+    }
+    .col-lg-push-3{
+        left:25%
+    }
+    .col-lg-push-2{
+        left:16.66666667%
+    }
+    .col-lg-push-1{
+        left:8.33333333%
+    }
+    .col-lg-push-0{
+        left:auto
+    }
+    .col-lg-offset-12{
+        margin-left:100%
+    }
+    .col-lg-offset-11{
+        margin-left:91.66666667%
+    }
+    .col-lg-offset-10{
+        margin-left:83.33333333%
+    }
+    .col-lg-offset-9{
+        margin-left:75%
+    }
+    .col-lg-offset-8{
+        margin-left:66.66666667%
+    }
+    .col-lg-offset-7{
+        margin-left:58.33333333%
+    }
+    .col-lg-offset-6{
+        margin-left:50%
+    }
+    .col-lg-offset-5{
+        margin-left:41.66666667%
+    }
+    .col-lg-offset-4{
+        margin-left:33.33333333%
+    }
+    .col-lg-offset-3{
+        margin-left:25%
+    }
+    .col-lg-offset-2{
+        margin-left:16.66666667%
+    }
+    .col-lg-offset-1{
+        margin-left:8.33333333%
+    }
+    .col-lg-offset-0{
+        margin-left:0
+    }
+}
+table{
+    background-color:transparent
+}
+caption{
+    padding-top:8px;
+    padding-bottom:8px;
+    color:#777;
+    text-align:left
+}
+th{
+    text-align:left
+}
+.table{
+    width:100%;
+    max-width:100%;
+    margin-bottom:20px
+}
+.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{
+    padding:8px;
+    line-height:1.42857143;
+    vertical-align:top;
+    border-top:1px solid #ddd
+}
+.table>thead>tr>th{
+    vertical-align:bottom;
+    border-bottom:2px solid #ddd
+}
+.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{
+    border-top:0
+}
+.table>tbody+tbody{
+    border-top:2px solid #ddd
+}
+.table .table{
+    background-color:#fff
+}
+.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{
+    padding:5px
+}
+.table-bordered{
+    border:1px solid #ddd
+}
+.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{
+    border:1px solid #ddd
+}
+.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{
+    border-bottom-width:2px
+}
+.table-striped>tbody>tr:nth-of-type(odd){
+    background-color:#f9f9f9
+}
+.table-hover>tbody>tr:hover{
+    background-color:#f5f5f5
+}
+table col[class*=col-]{
+    position:static;
+    display:table-column;
+    float:none
+}
+table td[class*=col-],table th[class*=col-]{
+    position:static;
+    display:table-cell;
+    float:none
+}
+.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{
+    background-color:#f5f5f5
+}
+.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{
+    background-color:#e8e8e8
+}
+.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{
+    background-color:#dff0d8
+}
+.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{
+    background-color:#d0e9c6
+}
+.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{
+    background-color:#d9edf7
+}
+.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{
+    background-color:#c4e3f3
+}
+.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{
+    background-color:#fcf8e3
+}
+.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{
+    background-color:#faf2cc
+}
+.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{
+    background-color:#f2dede
+}
+.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{
+    background-color:#ebcccc
+}
+.table-responsive{
+    min-height:.01%;
+    overflow-x:auto
+}
+@media screen and (max-width:767px){
+    .table-responsive{
+        width:100%;
+        margin-bottom:15px;
+        overflow-y:hidden;
+        -ms-overflow-style:-ms-autohiding-scrollbar;
+        border:1px solid #ddd
+    }
+    .table-responsive>.table{
+        margin-bottom:0
+    }
+    .table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{
+        white-space:nowrap
+    }
+    .table-responsive>.table-bordered{
+        border:0
+    }
+    .table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{
+        border-left:0
+    }
+    .table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{
+        border-right:0
+    }
+    .table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{
+        border-bottom:0
+    }
+}
+fieldset{
+    min-width:0;
+    padding:0;
+    margin:0;
+    border:0
+}
+legend{
+    display:block;
+    width:100%;
+    padding:0;
+    margin-bottom:20px;
+    font-size:21px;
+    line-height:inherit;
+    color:#333;
+    border:0;
+    border-bottom:1px solid #e5e5e5
+}
+label{
+    display:inline-block;
+    max-width:100%;
+    margin-bottom:5px;
+    font-weight:700
+}
+input[type=search]{
+    -webkit-box-sizing:border-box;
+    -moz-box-sizing:border-box;
+    box-sizing:border-box
+}
+input[type=checkbox],input[type=radio]{
+    margin:4px 0 0;
+    margin-top:1px\9;
+    line-height:normal
+}
+input[type=file]{
+    display:block
+}
+input[type=range]{
+    display:block;
+    width:100%
+}
+select[multiple],select[size]{
+    height:auto
+}
+input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{
+    outline:5px auto -webkit-focus-ring-color;
+    outline-offset:-2px
+}
+output{
+    display:block;
+    padding-top:7px;
+    font-size:14px;
+    line-height:1.42857143;
+    color:#555
+}
+.form-control{
+    display:block;
+    width:100%;
+    height:34px;
+    padding:6px 12px;
+    font-size:14px;
+    line-height:1.42857143;
+    color:#555;
+    background-color:#fff;
+    background-image:none;
+    border:1px solid #ccc;
+    border-radius:4px;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075);
+    -webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
+    -o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;
+    transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s
+}
+.form-control:focus{
+    border-color:#66afe9;
+    outline:0;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)
+}
+.form-control::-moz-placeholder{
+    color:#999;
+    opacity:1
+}
+.form-control:-ms-input-placeholder{
+    color:#999
+}
+.form-control::-webkit-input-placeholder{
+    color:#999
+}
+.form-control::-ms-expand{
+    background-color:transparent;
+    border:0
+}
+.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{
+    background-color:#eee;
+    opacity:1
+}
+.form-control[disabled],fieldset[disabled] .form-control{
+    cursor:not-allowed
+}
+textarea.form-control{
+    height:auto
+}
+input[type=search]{
+    -webkit-appearance:none
+}
+@media screen and (-webkit-min-device-pixel-ratio:0){
+    input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{
+        line-height:34px
+    }
+    .input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{
+        line-height:30px
+    }
+    .input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{
+        line-height:46px
+    }
+}
+.form-group{
+    margin-bottom:15px
+}
+.checkbox,.radio{
+    position:relative;
+    display:block;
+    margin-top:10px;
+    margin-bottom:10px
+}
+.checkbox label,.radio label{
+    min-height:20px;
+    padding-left:20px;
+    margin-bottom:0;
+    font-weight:400;
+    cursor:pointer
+}
+.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{
+    position:absolute;
+    margin-top:4px\9;
+    margin-left:-20px
+}
+.checkbox+.checkbox,.radio+.radio{
+    margin-top:-5px
+}
+.checkbox-inline,.radio-inline{
+    position:relative;
+    display:inline-block;
+    padding-left:20px;
+    margin-bottom:0;
+    font-weight:400;
+    vertical-align:middle;
+    cursor:pointer
+}
+.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{
+    margin-top:0;
+    margin-left:10px
+}
+fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{
+    cursor:not-allowed
+}
+.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{
+    cursor:not-allowed
+}
+.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{
+    cursor:not-allowed
+}
+.form-control-static{
+    min-height:34px;
+    padding-top:7px;
+    padding-bottom:7px;
+    margin-bottom:0
+}
+.form-control-static.input-lg,.form-control-static.input-sm{
+    padding-right:0;
+    padding-left:0
+}
+.input-sm{
+    height:30px;
+    padding:5px 10px;
+    font-size:12px;
+    line-height:1.5;
+    border-radius:3px
+}
+select.input-sm{
+    height:30px;
+    line-height:30px
+}
+select[multiple].input-sm,textarea.input-sm{
+    height:auto
+}
+.form-group-sm .form-control{
+    height:30px;
+    padding:5px 10px;
+    font-size:12px;
+    line-height:1.5;
+    border-radius:3px
+}
+.form-group-sm select.form-control{
+    height:30px;
+    line-height:30px
+}
+.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{
+    height:auto
+}
+.form-group-sm .form-control-static{
+    height:30px;
+    min-height:32px;
+    padding:6px 10px;
+    font-size:12px;
+    line-height:1.5
+}
+.input-lg{
+    height:46px;
+    padding:10px 16px;
+    font-size:18px;
+    line-height:1.3333333;
+    border-radius:6px
+}
+select.input-lg{
+    height:46px;
+    line-height:46px
+}
+select[multiple].input-lg,textarea.input-lg{
+    height:auto
+}
+.form-group-lg .form-control{
+    height:46px;
+    padding:10px 16px;
+    font-size:18px;
+    line-height:1.3333333;
+    border-radius:6px
+}
+.form-group-lg select.form-control{
+    height:46px;
+    line-height:46px
+}
+.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{
+    height:auto
+}
+.form-group-lg .form-control-static{
+    height:46px;
+    min-height:38px;
+    padding:11px 16px;
+    font-size:18px;
+    line-height:1.3333333
+}
+.has-feedback{
+    position:relative
+}
+.has-feedback .form-control{
+    padding-right:42.5px
+}
+.form-control-feedback{
+    position:absolute;
+    top:0;
+    right:0;
+    z-index:2;
+    display:block;
+    width:34px;
+    height:34px;
+    line-height:34px;
+    text-align:center;
+    pointer-events:none
+}
+.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{
+    width:46px;
+    height:46px;
+    line-height:46px
+}
+.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{
+    width:30px;
+    height:30px;
+    line-height:30px
+}
+.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{
+    color:#3c763d
+}
+.has-success .form-control{
+    border-color:#3c763d;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075)
+}
+.has-success .form-control:focus{
+    border-color:#2b542c;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168
+}
+.has-success .input-group-addon{
+    color:#3c763d;
+    background-color:#dff0d8;
+    border-color:#3c763d
+}
+.has-success .form-control-feedback{
+    color:#3c763d
+}
+.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{
+    color:#8a6d3b
+}
+.has-warning .form-control{
+    border-color:#8a6d3b;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075)
+}
+.has-warning .form-control:focus{
+    border-color:#66512c;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b
+}
+.has-warning .input-group-addon{
+    color:#8a6d3b;
+    background-color:#fcf8e3;
+    border-color:#8a6d3b
+}
+.has-warning .form-control-feedback{
+    color:#8a6d3b
+}
+.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{
+    color:#a94442
+}
+.has-error .form-control{
+    border-color:#a94442;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075)
+}
+.has-error .form-control:focus{
+    border-color:#843534;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483
+}
+.has-error .input-group-addon{
+    color:#a94442;
+    background-color:#f2dede;
+    border-color:#a94442
+}
+.has-error .form-control-feedback{
+    color:#a94442
+}
+.has-feedback label~.form-control-feedback{
+    top:25px
+}
+.has-feedback label.sr-only~.form-control-feedback{
+    top:0
+}
+.help-block{
+    display:block;
+    margin-top:5px;
+    margin-bottom:10px;
+    color:#737373
+}
+@media (min-width:768px){
+    .form-inline .form-group{
+        display:inline-block;
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .form-inline .form-control{
+        display:inline-block;
+        width:auto;
+        vertical-align:middle
+    }
+    .form-inline .form-control-static{
+        display:inline-block
+    }
+    .form-inline .input-group{
+        display:inline-table;
+        vertical-align:middle
+    }
+    .form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{
+        width:auto
+    }
+    .form-inline .input-group>.form-control{
+        width:100%
+    }
+    .form-inline .control-label{
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .form-inline .checkbox,.form-inline .radio{
+        display:inline-block;
+        margin-top:0;
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .form-inline .checkbox label,.form-inline .radio label{
+        padding-left:0
+    }
+    .form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{
+        position:relative;
+        margin-left:0
+    }
+    .form-inline .has-feedback .form-control-feedback{
+        top:0
+    }
+}
+.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{
+    padding-top:7px;
+    margin-top:0;
+    margin-bottom:0
+}
+.form-horizontal .checkbox,.form-horizontal .radio{
+    min-height:27px
+}
+.form-horizontal .form-group{
+    margin-right:-15px;
+    margin-left:-15px
+}
+@media (min-width:768px){
+    .form-horizontal .control-label{
+        padding-top:7px;
+        margin-bottom:0;
+        text-align:right
+    }
+}
+.form-horizontal .has-feedback .form-control-feedback{
+    right:15px
+}
+@media (min-width:768px){
+    .form-horizontal .form-group-lg .control-label{
+        padding-top:11px;
+        font-size:18px
+    }
+}
+@media (min-width:768px){
+    .form-horizontal .form-group-sm .control-label{
+        padding-top:6px;
+        font-size:12px
+    }
+}
+.btn{
+    display:inline-block;
+    padding:6px 12px;
+    margin-bottom:0;
+    font-size:14px;
+    font-weight:400;
+    line-height:1.42857143;
+    text-align:center;
+    white-space:nowrap;
+    vertical-align:middle;
+    -ms-touch-action:manipulation;
+    touch-action:manipulation;
+    cursor:pointer;
+    -webkit-user-select:none;
+    -moz-user-select:none;
+    -ms-user-select:none;
+    user-select:none;
+    background-image:none;
+    border:1px solid transparent;
+    border-radius:4px
+}
+.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{
+    outline:5px auto -webkit-focus-ring-color;
+    outline-offset:-2px
+}
+.btn.focus,.btn:focus,.btn:hover{
+    color:#333;
+    text-decoration:none
+}
+.btn.active,.btn:active{
+    background-image:none;
+    outline:0;
+    -webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);
+    box-shadow:inset 0 3px 5px rgba(0,0,0,.125)
+}
+.btn.disabled,.btn[disabled],fieldset[disabled] .btn{
+    cursor:not-allowed;
+    filter:alpha(opacity=65);
+    -webkit-box-shadow:none;
+    box-shadow:none;
+    opacity:.65
+}
+a.btn.disabled,fieldset[disabled] a.btn{
+    pointer-events:none
+}
+.btn-default{
+    color:#333;
+    background-color:#fff;
+    border-color:#ccc
+}
+.btn-default.focus,.btn-default:focus{
+    color:#333;
+    background-color:#e6e6e6;
+    border-color:#8c8c8c
+}
+.btn-default:hover{
+    color:#333;
+    background-color:#e6e6e6;
+    border-color:#adadad
+}
+.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{
+    color:#333;
+    background-color:#e6e6e6;
+    border-color:#adadad
+}
+.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{
+    color:#333;
+    background-color:#d4d4d4;
+    border-color:#8c8c8c
+}
+.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{
+    background-image:none
+}
+.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{
+    background-color:#fff;
+    border-color:#ccc
+}
+.btn-default .badge{
+    color:#fff;
+    background-color:#333
+}
+.btn-primary{
+    color:#fff;
+    background-color:#d11010;
+    border-color:#c40f0f
+}
+.btn-primary.focus,.btn-primary:focus{
+    color:#fff;
+    background-color:#b20c0c;
+    border-color:#c40f0f
+}
+.btn-primary:hover{
+    color:#fff;
+    background-color:#b20c0c;
+    border-color:#c40f0f
+}
+.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{
+    color:#fff;
+    background-color:#b20c0c;
+    border-color:#c40f0f
+}
+.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{
+    color:#fff;
+    background-color:#b20c0c;
+    border-color:#c40f0f
+}
+.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{
+    background-image:none
+}
+.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{
+    background-color:#b20c0c;
+    border-color:#c40f0f
+}
+.btn-primary .badge{
+    color:#337ab7;
+    background-color:#fff
+}
+.btn-success{
+    color:#fff;
+    background-color:#5cb85c;
+    border-color:#4cae4c
+}
+.btn-success.focus,.btn-success:focus{
+    color:#fff;
+    background-color:#449d44;
+    border-color:#255625
+}
+.btn-success:hover{
+    color:#fff;
+    background-color:#449d44;
+    border-color:#398439
+}
+.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{
+    color:#fff;
+    background-color:#449d44;
+    border-color:#398439
+}
+.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{
+    color:#fff;
+    background-color:#398439;
+    border-color:#255625
+}
+.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{
+    background-image:none
+}
+.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{
+    background-color:#5cb85c;
+    border-color:#4cae4c
+}
+.btn-success .badge{
+    color:#5cb85c;
+    background-color:#fff
+}
+.btn-info{
+    color:#fff;
+    background-color:#5bc0de;
+    border-color:#46b8da
+}
+.btn-info.focus,.btn-info:focus{
+    color:#fff;
+    background-color:#31b0d5;
+    border-color:#1b6d85
+}
+.btn-info:hover{
+    color:#fff;
+    background-color:#31b0d5;
+    border-color:#269abc
+}
+.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{
+    color:#fff;
+    background-color:#31b0d5;
+    border-color:#269abc
+}
+.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{
+    color:#fff;
+    background-color:#269abc;
+    border-color:#1b6d85
+}
+.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{
+    background-image:none
+}
+.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{
+    background-color:#5bc0de;
+    border-color:#46b8da
+}
+.btn-info .badge{
+    color:#5bc0de;
+    background-color:#fff
+}
+.btn-warning{
+    color:#fff;
+    background-color:#f0ad4e;
+    border-color:#eea236
+}
+.btn-warning.focus,.btn-warning:focus{
+    color:#fff;
+    background-color:#ec971f;
+    border-color:#985f0d
+}
+.btn-warning:hover{
+    color:#fff;
+    background-color:#ec971f;
+    border-color:#d58512
+}
+.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{
+    color:#fff;
+    background-color:#ec971f;
+    border-color:#d58512
+}
+.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{
+    color:#fff;
+    background-color:#d58512;
+    border-color:#985f0d
+}
+.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{
+    background-image:none
+}
+.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{
+    background-color:#f0ad4e;
+    border-color:#eea236
+}
+.btn-warning .badge{
+    color:#f0ad4e;
+    background-color:#fff
+}
+.btn-danger{
+    color:#fff;
+    background-color:#d9534f;
+    border-color:#d43f3a
+}
+.btn-danger.focus,.btn-danger:focus{
+    color:#fff;
+    background-color:#c9302c;
+    border-color:#761c19
+}
+.btn-danger:hover{
+    color:#fff;
+    background-color:#c9302c;
+    border-color:#ac2925
+}
+.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{
+    color:#fff;
+    background-color:#c9302c;
+    border-color:#ac2925
+}
+.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{
+    color:#fff;
+    background-color:#ac2925;
+    border-color:#761c19
+}
+.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{
+    background-image:none
+}
+.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{
+    background-color:#d9534f;
+    border-color:#d43f3a
+}
+.btn-danger .badge{
+    color:#d9534f;
+    background-color:#fff
+}
+.btn-link{
+    font-weight:400;
+    color:#337ab7;
+    border-radius:0
+}
+.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{
+    background-color:transparent;
+    -webkit-box-shadow:none;
+    box-shadow:none
+}
+.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{
+    border-color:transparent
+}
+.btn-link:focus,.btn-link:hover{
+    color:#23527c;
+    text-decoration:underline;
+    background-color:transparent
+}
+.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{
+    color:#777;
+    text-decoration:none
+}
+.btn-group-lg>.btn,.btn-lg{
+    padding:10px 16px;
+    font-size:18px;
+    line-height:1.3333333;
+    border-radius:6px
+}
+.btn-group-sm>.btn,.btn-sm{
+    padding:5px 10px;
+    font-size:12px;
+    line-height:1.5;
+    border-radius:3px
+}
+.btn-group-xs>.btn,.btn-xs{
+    padding:1px 5px;
+    font-size:12px;
+    line-height:1.5;
+    border-radius:3px
+}
+.btn-block{
+    display:block;
+    width:100%
+}
+.btn-block+.btn-block{
+    margin-top:5px
+}
+input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{
+    width:100%
+}
+.fade{
+    opacity:0;
+    -webkit-transition:opacity .15s linear;
+    -o-transition:opacity .15s linear;
+    transition:opacity .15s linear
+}
+.fade.in{
+    opacity:1
+}
+.collapse{
+    display:none
+}
+.collapse.in{
+    display:block
+}
+tr.collapse.in{
+    display:table-row
+}
+tbody.collapse.in{
+    display:table-row-group
+}
+.collapsing{
+    position:relative;
+    height:0;
+    overflow:hidden;
+    -webkit-transition-timing-function:ease;
+    -o-transition-timing-function:ease;
+    transition-timing-function:ease;
+    -webkit-transition-duration:.35s;
+    -o-transition-duration:.35s;
+    transition-duration:.35s;
+    -webkit-transition-property:height,visibility;
+    -o-transition-property:height,visibility;
+    transition-property:height,visibility
+}
+.caret{
+    display:inline-block;
+    width:0;
+    height:0;
+    margin-left:2px;
+    vertical-align:middle;
+    border-top:4px dashed;
+    border-top:4px solid\9;
+    border-right:4px solid transparent;
+    border-left:4px solid transparent
+}
+.dropdown,.dropup{
+    position:relative
+}
+.dropdown-toggle:focus{
+    outline:0
+}
+.dropdown-menu{
+    position:absolute;
+    top:100%;
+    left:0;
+    z-index:1000;
+    display:none;
+    float:left;
+    min-width:160px;
+    padding:5px 0;
+    margin:2px 0 0;
+    font-size:14px;
+    text-align:left;
+    list-style:none;
+    background-color:#fff;
+    -webkit-background-clip:padding-box;
+    background-clip:padding-box;
+    border:1px solid #ccc;
+    border:1px solid rgba(0,0,0,.15);
+    border-radius:4px;
+    -webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);
+    box-shadow:0 6px 12px rgba(0,0,0,.175)
+}
+.dropdown-menu.pull-right{
+    right:0;
+    left:auto
+}
+.dropdown-menu .divider{
+    height:1px;
+    margin:9px 0;
+    overflow:hidden;
+    background-color:#e5e5e5
+}
+.dropdown-menu>li>a{
+    display:block;
+    padding:3px 20px;
+    clear:both;
+    font-weight:400;
+    line-height:1.42857143;
+    color:#333;
+    white-space:nowrap
+}
+.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{
+    color:#262626;
+    text-decoration:none;
+    background-color:#f5f5f5
+}
+.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{
+    color:#fff;
+    text-decoration:none;
+    background-color:#337ab7;
+    outline:0
+}
+.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{
+    color:#777
+}
+.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{
+    text-decoration:none;
+    cursor:not-allowed;
+    background-color:transparent;
+    background-image:none;
+    filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)
+}
+.open>.dropdown-menu{
+    display:block
+}
+.open>a{
+    outline:0
+}
+.dropdown-menu-right{
+    right:0;
+    left:auto
+}
+.dropdown-menu-left{
+    right:auto;
+    left:0
+}
+.dropdown-header{
+    display:block;
+    padding:3px 20px;
+    font-size:12px;
+    line-height:1.42857143;
+    color:#777;
+    white-space:nowrap
+}
+.dropdown-backdrop{
+    position:fixed;
+    top:0;
+    right:0;
+    bottom:0;
+    left:0;
+    z-index:990
+}
+.pull-right>.dropdown-menu{
+    right:0;
+    left:auto
+}
+.dropup .caret,.navbar-fixed-bottom .dropdown .caret{
+    content:"";
+    border-top:0;
+    border-bottom:4px dashed;
+    border-bottom:4px solid\9
+}
+.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{
+    top:auto;
+    bottom:100%;
+    margin-bottom:2px
+}
+@media (min-width:768px){
+    .navbar-right .dropdown-menu{
+        right:0;
+        left:auto
+    }
+    .navbar-right .dropdown-menu-left{
+        right:auto;
+        left:0
+    }
+}
+.btn-group,.btn-group-vertical{
+    position:relative;
+    display:inline-block;
+    vertical-align:middle
+}
+.btn-group-vertical>.btn,.btn-group>.btn{
+    position:relative;
+    float:left
+}
+.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{
+    z-index:2
+}
+.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{
+    margin-left:-1px
+}
+.btn-toolbar{
+    margin-left:-5px
+}
+.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{
+    float:left
+}
+.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{
+    margin-left:5px
+}
+.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){
+    border-radius:0
+}
+.btn-group>.btn:first-child{
+    margin-left:0
+}
+.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){
+    border-top-right-radius:0;
+    border-bottom-right-radius:0
+}
+.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){
+    border-top-left-radius:0;
+    border-bottom-left-radius:0
+}
+.btn-group>.btn-group{
+    float:left
+}
+.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{
+    border-radius:0
+}
+.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{
+    border-top-right-radius:0;
+    border-bottom-right-radius:0
+}
+.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{
+    border-top-left-radius:0;
+    border-bottom-left-radius:0
+}
+.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{
+    outline:0
+}
+.btn-group>.btn+.dropdown-toggle{
+    padding-right:8px;
+    padding-left:8px
+}
+.btn-group>.btn-lg+.dropdown-toggle{
+    padding-right:12px;
+    padding-left:12px
+}
+.btn-group.open .dropdown-toggle{
+    -webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);
+    box-shadow:inset 0 3px 5px rgba(0,0,0,.125)
+}
+.btn-group.open .dropdown-toggle.btn-link{
+    -webkit-box-shadow:none;
+    box-shadow:none
+}
+.btn .caret{
+    margin-left:0
+}
+.btn-lg .caret{
+    border-width:5px 5px 0;
+    border-bottom-width:0
+}
+.dropup .btn-lg .caret{
+    border-width:0 5px 5px
+}
+.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{
+    display:block;
+    float:none;
+    width:100%;
+    max-width:100%
+}
+.btn-group-vertical>.btn-group>.btn{
+    float:none
+}
+.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{
+    margin-top:-1px;
+    margin-left:0
+}
+.btn-group-vertical>.btn:not(:first-child):not(:last-child){
+    border-radius:0
+}
+.btn-group-vertical>.btn:first-child:not(:last-child){
+    border-top-left-radius:4px;
+    border-top-right-radius:4px;
+    border-bottom-right-radius:0;
+    border-bottom-left-radius:0
+}
+.btn-group-vertical>.btn:last-child:not(:first-child){
+    border-top-left-radius:0;
+    border-top-right-radius:0;
+    border-bottom-right-radius:4px;
+    border-bottom-left-radius:4px
+}
+.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{
+    border-radius:0
+}
+.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{
+    border-bottom-right-radius:0;
+    border-bottom-left-radius:0
+}
+.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{
+    border-top-left-radius:0;
+    border-top-right-radius:0
+}
+.btn-group-justified{
+    display:table;
+    width:100%;
+    table-layout:fixed;
+    border-collapse:separate
+}
+.btn-group-justified>.btn,.btn-group-justified>.btn-group{
+    display:table-cell;
+    float:none;
+    width:1%
+}
+.btn-group-justified>.btn-group .btn{
+    width:100%
+}
+.btn-group-justified>.btn-group .dropdown-menu{
+    left:auto
+}
+[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{
+    position:absolute;
+    clip:rect(0,0,0,0);
+    pointer-events:none
+}
+.input-group{
+    position:relative;
+    display:table;
+    border-collapse:separate
+}
+.input-group[class*=col-]{
+    float:none;
+    padding-right:0;
+    padding-left:0
+}
+.input-group .form-control{
+    position:relative;
+    z-index:2;
+    float:left;
+    width:100%;
+    margin-bottom:0
+}
+.input-group .form-control:focus{
+    z-index:3
+}
+.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{
+    height:46px;
+    padding:10px 16px;
+    font-size:18px;
+    line-height:1.3333333;
+    border-radius:6px
+}
+select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{
+    height:46px;
+    line-height:46px
+}
+select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{
+    height:auto
+}
+.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{
+    height:30px;
+    padding:5px 10px;
+    font-size:12px;
+    line-height:1.5;
+    border-radius:3px
+}
+select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{
+    height:30px;
+    line-height:30px
+}
+select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{
+    height:auto
+}
+.input-group .form-control,.input-group-addon,.input-group-btn{
+    display:table-cell
+}
+.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){
+    border-radius:0
+}
+.input-group-addon,.input-group-btn{
+    width:1%;
+    white-space:nowrap;
+    vertical-align:middle
+}
+.input-group-addon{
+    padding:6px 12px;
+    font-size:14px;
+    font-weight:400;
+    line-height:1;
+    color:#555;
+    text-align:center;
+    background-color:#eee;
+    border:1px solid #ccc;
+    border-radius:4px
+}
+.input-group-addon.input-sm{
+    padding:5px 10px;
+    font-size:12px;
+    border-radius:3px
+}
+.input-group-addon.input-lg{
+    padding:10px 16px;
+    font-size:18px;
+    border-radius:6px
+}
+.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{
+    margin-top:0
+}
+.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){
+    border-top-right-radius:0;
+    border-bottom-right-radius:0
+}
+.input-group-addon:first-child{
+    border-right:0
+}
+.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{
+    border-top-left-radius:0;
+    border-bottom-left-radius:0
+}
+.input-group-addon:last-child{
+    border-left:0
+}
+.input-group-btn{
+    position:relative;
+    font-size:0;
+    white-space:nowrap
+}
+.input-group-btn>.btn{
+    position:relative
+}
+.input-group-btn>.btn+.btn{
+    margin-left:-1px
+}
+.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{
+    z-index:2
+}
+.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{
+    margin-right:-1px
+}
+.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{
+    z-index:2;
+    margin-left:-1px
+}
+.nav{
+    padding-left:0;
+    margin-bottom:0;
+    list-style:none
+}
+.nav>li{
+    position:relative;
+    display:block
+}
+.nav>li>a{
+    position:relative;
+    display:block;
+    padding:10px 15px
+}
+.nav>li>a:focus,.nav>li>a:hover{
+    text-decoration:none;
+    background-color:#eee
+}
+.nav>li.disabled>a{
+    color:#777
+}
+.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{
+    color:#777;
+    text-decoration:none;
+    cursor:not-allowed;
+    background-color:transparent
+}
+.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{
+    background-color:#eee;
+    border-color:#337ab7
+}
+.nav .nav-divider{
+    height:1px;
+    margin:9px 0;
+    overflow:hidden;
+    background-color:#e5e5e5
+}
+.nav>li>a>img{
+    max-width:none
+}
+.nav-tabs{
+    border-bottom:1px solid #ddd
+}
+.nav-tabs>li{
+    float:left;
+    margin-bottom:-1px
+}
+.nav-tabs>li>a{
+    margin-right:2px;
+    line-height:1.42857143;
+    border:1px solid transparent;
+    border-radius:4px 4px 0 0
+}
+.nav-tabs>li>a:hover{
+    border-color:#eee #eee #ddd
+}
+.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{
+    color:#555;
+    cursor:default;
+    background-color:#fff;
+    border:1px solid #ddd;
+    border-bottom-color:transparent
+}
+.nav-tabs.nav-justified{
+    width:100%;
+    border-bottom:0
+}
+.nav-tabs.nav-justified>li{
+    float:none
+}
+.nav-tabs.nav-justified>li>a{
+    margin-bottom:5px;
+    text-align:center
+}
+.nav-tabs.nav-justified>.dropdown .dropdown-menu{
+    top:auto;
+    left:auto
+}
+@media (min-width:768px){
+    .nav-tabs.nav-justified>li{
+        display:table-cell;
+        width:1%
+    }
+    .nav-tabs.nav-justified>li>a{
+        margin-bottom:0
+    }
+}
+.nav-tabs.nav-justified>li>a{
+    margin-right:0;
+    border-radius:4px
+}
+.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{
+    border:1px solid #ddd
+}
+@media (min-width:768px){
+    .nav-tabs.nav-justified>li>a{
+        border-bottom:1px solid #ddd;
+        border-radius:4px 4px 0 0
+    }
+    .nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{
+        border-bottom-color:#fff
+    }
+}
+.nav-pills>li{
+    float:left
+}
+.nav-pills>li>a{
+    border-radius:4px
+}
+.nav-pills>li+li{
+    margin-left:2px
+}
+.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{
+    color:#fff;
+    background-color:#337ab7
+}
+.nav-stacked>li{
+    float:none
+}
+.nav-stacked>li+li{
+    margin-top:2px;
+    margin-left:0
+}
+.nav-justified{
+    width:100%
+}
+.nav-justified>li{
+    float:none
+}
+.nav-justified>li>a{
+    margin-bottom:5px;
+    text-align:center
+}
+.nav-justified>.dropdown .dropdown-menu{
+    top:auto;
+    left:auto
+}
+@media (min-width:768px){
+    .nav-justified>li{
+        display:table-cell;
+        width:1%
+    }
+    .nav-justified>li>a{
+        margin-bottom:0
+    }
+}
+.nav-tabs-justified{
+    border-bottom:0
+}
+.nav-tabs-justified>li>a{
+    margin-right:0;
+    border-radius:4px
+}
+.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{
+    border:1px solid #ddd
+}
+@media (min-width:768px){
+    .nav-tabs-justified>li>a{
+        border-bottom:1px solid #ddd;
+        border-radius:4px 4px 0 0
+    }
+    .nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{
+        border-bottom-color:#fff
+    }
+}
+.tab-content>.tab-pane{
+    display:none
+}
+.tab-content>.active{
+    display:block
+}
+.nav-tabs .dropdown-menu{
+    margin-top:-1px;
+    border-top-left-radius:0;
+    border-top-right-radius:0
+}
+.navbar{
+    position:relative;
+    min-height:50px;
+    margin-bottom:20px;
+    border:1px solid transparent
+}
+@media (min-width:768px){
+    .navbar{
+        border-radius:4px
+    }
+}
+@media (min-width:768px){
+    .navbar-header{
+        float:left
+    }
+}
+.navbar-collapse{
+    padding-right:15px;
+    padding-left:15px;
+    overflow-x:visible;
+    -webkit-overflow-scrolling:touch;
+    border-top:1px solid transparent;
+    -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);
+    box-shadow:inset 0 1px 0 rgba(255,255,255,.1)
+}
+.navbar-collapse.in{
+    overflow-y:auto
+}
+@media (min-width:768px){
+    .navbar-collapse{
+        width:auto;
+        border-top:0;
+        -webkit-box-shadow:none;
+        box-shadow:none
+    }
+    .navbar-collapse.collapse{
+        display:block!important;
+        height:auto!important;
+        padding-bottom:0;
+        overflow:visible!important
+    }
+    .navbar-collapse.in{
+        overflow-y:visible
+    }
+    .navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{
+        padding-right:0;
+        padding-left:0
+    }
+}
+.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{
+    max-height:340px
+}
+@media (max-device-width:480px) and (orientation:landscape){
+    .navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{
+        max-height:200px
+    }
+}
+.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{
+    margin-right:-15px;
+    margin-left:-15px
+}
+@media (min-width:768px){
+    .container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{
+        margin-right:0;
+        margin-left:0
+    }
+}
+.navbar-static-top{
+    z-index:1000;
+    border-width:0 0 1px
+}
+@media (min-width:768px){
+    .navbar-static-top{
+        border-radius:0
+    }
+}
+.navbar-fixed-bottom,.navbar-fixed-top{
+    position:fixed;
+    right:0;
+    left:0;
+    z-index:1030
+}
+@media (min-width:768px){
+    .navbar-fixed-bottom,.navbar-fixed-top{
+        border-radius:0
+    }
+}
+.navbar-fixed-top{
+    top:0;
+    border-width:0 0 1px
+}
+.navbar-fixed-bottom{
+    bottom:0;
+    margin-bottom:0;
+    border-width:1px 0 0
+}
+.navbar-brand{
+    float:left;
+    height:50px;
+    padding:15px 15px;
+    font-size:18px;
+    line-height:20px
+}
+.navbar-brand:focus,.navbar-brand:hover{
+    text-decoration:none
+}
+.navbar-brand>img{
+    display:block
+}
+@media (min-width:768px){
+    .navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{
+        margin-left:-15px
+    }
+}
+.navbar-toggle{
+    position:relative;
+    float:right;
+    padding:9px 10px;
+    margin-top:8px;
+    margin-right:15px;
+    margin-bottom:8px;
+    background-color:transparent;
+    background-image:none;
+    border:1px solid transparent;
+    border-radius:4px
+}
+.navbar-toggle:focus{
+    outline:0
+}
+.navbar-toggle .icon-bar{
+    display:block;
+    width:22px;
+    height:2px;
+    border-radius:1px
+}
+.navbar-toggle .icon-bar+.icon-bar{
+    margin-top:4px
+}
+@media (min-width:768px){
+    .navbar-toggle{
+        display:none
+    }
+}
+.navbar-nav{
+    margin:7.5px -15px
+}
+.navbar-nav>li>a{
+    padding-top:10px;
+    padding-bottom:10px;
+    line-height:20px
+}
+@media (max-width:767px){
+    .navbar-nav .open .dropdown-menu{
+        position:static;
+        float:none;
+        width:auto;
+        margin-top:0;
+        background-color:transparent;
+        border:0;
+        -webkit-box-shadow:none;
+        box-shadow:none
+    }
+    .navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{
+        padding:5px 15px 5px 25px
+    }
+    .navbar-nav .open .dropdown-menu>li>a{
+        line-height:20px
+    }
+    .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{
+        background-image:none
+    }
+}
+@media (min-width:768px){
+    .navbar-nav{
+        float:left;
+        margin:0
+    }
+    .navbar-nav>li{
+        float:left
+    }
+    .navbar-nav>li>a{
+        padding-top:15px;
+        padding-bottom:15px
+    }
+}
+.navbar-form{
+    padding:10px 15px;
+    margin-top:8px;
+    margin-right:-15px;
+    margin-bottom:8px;
+    margin-left:-15px;
+    border-top:1px solid transparent;
+    border-bottom:1px solid transparent;
+    -webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);
+    box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)
+}
+@media (min-width:768px){
+    .navbar-form .form-group{
+        display:inline-block;
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .navbar-form .form-control{
+        display:inline-block;
+        width:auto;
+        vertical-align:middle
+    }
+    .navbar-form .form-control-static{
+        display:inline-block
+    }
+    .navbar-form .input-group{
+        display:inline-table;
+        vertical-align:middle
+    }
+    .navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{
+        width:auto
+    }
+    .navbar-form .input-group>.form-control{
+        width:100%
+    }
+    .navbar-form .control-label{
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .navbar-form .checkbox,.navbar-form .radio{
+        display:inline-block;
+        margin-top:0;
+        margin-bottom:0;
+        vertical-align:middle
+    }
+    .navbar-form .checkbox label,.navbar-form .radio label{
+        padding-left:0
+    }
+    .navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{
+        position:relative;
+        margin-left:0
+    }
+    .navbar-form .has-feedback .form-control-feedback{
+        top:0
+    }
+}
+@media (max-width:767px){
+    .navbar-form .form-group{
+        margin-bottom:5px
+    }
+    .navbar-form .form-group:last-child{
+        margin-bottom:0
+    }
+}
+@media (min-width:768px){
+    .navbar-form{
+        width:auto;
+        padding-top:0;
+        padding-bottom:0;
+        margin-right:0;
+        margin-left:0;
+        border:0;
+        -webkit-box-shadow:none;
+        box-shadow:none
+    }
+}
+.navbar-nav>li>.dropdown-menu{
+    margin-top:0;
+    border-top-left-radius:0;
+    border-top-right-radius:0
+}
+.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{
+    margin-bottom:0;
+    border-top-left-radius:4px;
+    border-top-right-radius:4px;
+    border-bottom-right-radius:0;
+    border-bottom-left-radius:0
+}
+.navbar-btn{
+    margin-top:8px;
+    margin-bottom:8px
+}
+.navbar-btn.btn-sm{
+    margin-top:10px;
+    margin-bottom:10px
+}
+.navbar-btn.btn-xs{
+    margin-top:14px;
+    margin-bottom:14px
+}
+.navbar-text{
+    margin-top:15px;
+    margin-bottom:15px
+}
+@media (min-width:768px){
+    .navbar-text{
+        float:left;
+        margin-right:15px;
+        margin-left:15px
+    }
+}
+@media (min-width:768px){
+    .navbar-left{
+        float:left!important
+    }
+    .navbar-right{
+        float:right!important;
+        margin-right:-15px
+    }
+    .navbar-right~.navbar-right{
+        margin-right:0
+    }
+}
+.navbar-default{
+    background-color:#f8f8f8;
+    border-color:#e7e7e7
+}
+.navbar-default .navbar-brand{
+    color:#777
+}
+.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{
+    color:#5e5e5e;
+    background-color:transparent
+}
+.navbar-default .navbar-text{
+    color:#777
+}
+.navbar-default .navbar-nav>li>a{
+    color:#777
+}
+.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{
+    color:#333;
+    background-color:transparent
+}
+.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{
+    color:#555;
+    background-color:#e7e7e7
+}
+.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{
+    color:#ccc;
+    background-color:transparent
+}
+.navbar-default .navbar-toggle{
+    border-color:#ddd
+}
+.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{
+    background-color:#ddd
+}
+.navbar-default .navbar-toggle .icon-bar{
+    background-color:#888
+}
+.navbar-default .navbar-collapse,.navbar-default .navbar-form{
+    border-color:#e7e7e7
+}
+.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{
+    color:#555;
+    background-color:#e7e7e7
+}
+@media (max-width:767px){
+    .navbar-default .navbar-nav .open .dropdown-menu>li>a{
+        color:#777
+    }
+    .navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{
+        color:#333;
+        background-color:transparent
+    }
+    .navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{
+        color:#555;
+        background-color:#e7e7e7
+    }
+    .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{
+        color:#ccc;
+        background-color:transparent
+    }
+}
+.navbar-default .navbar-link{
+    color:#777
+}
+.navbar-default .navbar-link:hover{
+    color:#333
+}
+.navbar-default .btn-link{
+    color:#777
+}
+.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{
+    color:#333
+}
+.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{
+    color:#ccc
+}
+.navbar-inverse{
+    background-color:#222;
+    border-color:#080808
+}
+.navbar-inverse .navbar-brand{
+    color:#9d9d9d
+}
+.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{
+    color:#fff;
+    background-color:transparent
+}
+.navbar-inverse .navbar-text{
+    color:#9d9d9d
+}
+.navbar-inverse .navbar-nav>li>a{
+    color:#9d9d9d
+}
+.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{
+    color:#fff;
+    background-color:transparent
+}
+.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{
+    color:#fff;
+    background-color:#080808
+}
+.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{
+    color:#444;
+    background-color:transparent
+}
+.navbar-inverse .navbar-toggle{
+    border-color:#333
+}
+.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{
+    background-color:#333
+}
+.navbar-inverse .navbar-toggle .icon-bar{
+    background-color:#fff
+}
+.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{
+    border-color:#101010
+}
+.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{
+    color:#fff;
+    background-color:#080808
+}
+@media (max-width:767px){
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{
+        border-color:#080808
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu .divider{
+        background-color:#080808
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>li>a{
+        color:#9d9d9d
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{
+        color:#fff;
+        background-color:transparent
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{
+        color:#fff;
+        background-color:#080808
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{
+        color:#444;
+        background-color:transparent
+    }
+}
+.navbar-inverse .navbar-link{
+    color:#9d9d9d
+}
+.navbar-inverse .navbar-link:hover{
+    color:#fff
+}
+.navbar-inverse .btn-link{
+    color:#9d9d9d
+}
+.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{
+    color:#fff
+}
+.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{
+    color:#444
+}
+.breadcrumb{
+    padding:8px 15px;
+    margin-bottom:20px;
+    list-style:none;
+    background-color:#f5f5f5;
+    border-radius:4px
+}
+.breadcrumb>li{
+    display:inline-block
+}
+.breadcrumb>li+li:before{
+    padding:0 5px;
+    color:#ccc;
+    content:"/\00a0"
+}
+.breadcrumb>.active{
+    color:#777
+}
+.pagination{
+    display:inline-block;
+    padding-left:0;
+    margin:20px 0;
+    border-radius:4px
+}
+.pagination>li{
+    display:inline
+}
+.pagination>li>a,.pagination>li>span{
+    position:relative;
+    float:left;
+    padding:6px 12px;
+    margin-left:-1px;
+    line-height:1.42857143;
+    color:#337ab7;
+    text-decoration:none;
+    background-color:#fff;
+    border:1px solid #ddd
+}
+.pagination>li:first-child>a,.pagination>li:first-child>span{
+    margin-left:0;
+    border-top-left-radius:4px;
+    border-bottom-left-radius:4px
+}
+.pagination>li:last-child>a,.pagination>li:last-child>span{
+    border-top-right-radius:4px;
+    border-bottom-right-radius:4px
+}
+.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{
+    z-index:2;
+    color:#23527c;
+    background-color:#eee;
+    border-color:#ddd
+}
+.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{
+    z-index:3;
+    color:#fff;
+    cursor:default;
+    background-color:#337ab7;
+    border-color:#337ab7
+}
+.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{
+    color:#777;
+    cursor:not-allowed;
+    background-color:#fff;
+    border-color:#ddd
+}
+.pagination-lg>li>a,.pagination-lg>li>span{
+    padding:10px 16px;
+    font-size:18px;
+    line-height:1.3333333
+}
+.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{
+    border-top-left-radius:6px;
+    border-bottom-left-radius:6px
+}
+.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{
+    border-top-right-radius:6px;
+    border-bottom-right-radius:6px
+}
+.pagination-sm>li>a,.pagination-sm>li>span{
+    padding:5px 10px;
+    font-size:12px;
+    line-height:1.5
+}
+.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{
+    border-top-left-radius:3px;
+    border-bottom-left-radius:3px
+}
+.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{
+    border-top-right-radius:3px;
+    border-bottom-right-radius:3px
+}
+.pager{
+    padding-left:0;
+    margin:20px 0;
+    text-align:center;
+    list-style:none
+}
+.pager li{
+    display:inline
+}
+.pager li>a,.pager li>span{
+    display:inline-block;
+    padding:5px 14px;
+    background-color:#fff;
+    border:1px solid #ddd;
+    border-radius:15px
+}
+.pager li>a:focus,.pager li>a:hover{
+    text-decoration:none;
+    background-color:#eee
+}
+.pager .next>a,.pager .next>span{
+    float:right
+}
+.pager .previous>a,.pager .previous>span{
+    float:left
+}
+.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{
+    color:#777;
+    cursor:not-allowed;
+    background-color:#fff
+}
+.label{
+    display:inline;
+    padding:.2em .6em .3em;
+    font-size:75%;
+    font-weight:700;
+    line-height:1;
+    color:#fff;
+    text-align:center;
+    white-space:nowrap;
+    vertical-align:baseline;
+    border-radius:.25em
+}
+a.label:focus,a.label:hover{
+    color:#fff;
+    text-decoration:none;
+    cursor:pointer
+}
+.label:empty{
+    display:none
+}
+.btn .label{
+    position:relative;
+    top:-1px
+}
+.label-default{
+    background-color:#777
+}
+.label-default[href]:focus,.label-default[href]:hover{
+    background-color:#5e5e5e
+}
+.label-primary{
+    background-color:#337ab7
+}
+.label-primary[href]:focus,.label-primary[href]:hover{
+    background-color:#286090
+}
+.label-success{
+    background-color:#5cb85c
+}
+.label-success[href]:focus,.label-success[href]:hover{
+    background-color:#449d44
+}
+.label-info{
+    background-color:#5bc0de
+}
+.label-info[href]:focus,.label-info[href]:hover{
+    background-color:#31b0d5
+}
+.label-warning{
+    background-color:#f0ad4e
+}
+.label-warning[href]:focus,.label-warning[href]:hover{
+    background-color:#ec971f
+}
+.label-danger{
+    background-color:#d9534f
+}
+.label-danger[href]:focus,.label-danger[href]:hover{
+    background-color:#c9302c
+}
+.badge{
+    display:inline-block;
+    min-width:10px;
+    padding:3px 7px;
+    font-size:12px;
+    font-weight:700;
+    line-height:1;
+    color:#fff;
+    text-align:center;
+    white-space:nowrap;
+    vertical-align:middle;
+    background-color:#777;
+    border-radius:10px
+}
+.badge:empty{
+    display:none
+}
+.btn .badge{
+    position:relative;
+    top:-1px
+}
+.btn-group-xs>.btn .badge,.btn-xs .badge{
+    top:0;
+    padding:1px 5px
+}
+a.badge:focus,a.badge:hover{
+    color:#fff;
+    text-decoration:none;
+    cursor:pointer
+}
+.list-group-item.active>.badge,.nav-pills>.active>a>.badge{
+    color:#337ab7;
+    background-color:#fff
+}
+.list-group-item>.badge{
+    float:right
+}
+.list-group-item>.badge+.badge{
+    margin-right:5px
+}
+.nav-pills>li>a>.badge{
+    margin-left:3px
+}
+.jumbotron{
+    padding-top:30px;
+    padding-bottom:30px;
+    margin-bottom:30px;
+    color:inherit;
+    background-color:#eee
+}
+.jumbotron .h1,.jumbotron h1{
+    color:inherit
+}
+.jumbotron p{
+    margin-bottom:15px;
+    font-size:21px;
+    font-weight:200
+}
+.jumbotron>hr{
+    border-top-color:#d5d5d5
+}
+.container .jumbotron,.container-fluid .jumbotron{
+    padding-right:15px;
+    padding-left:15px;
+    border-radius:6px
+}
+.jumbotron .container{
+    max-width:100%
+}
+@media screen and (min-width:768px){
+    .jumbotron{
+        padding-top:48px;
+        padding-bottom:48px
+    }
+    .container .jumbotron,.container-fluid .jumbotron{
+        padding-right:60px;
+        padding-left:60px
+    }
+    .jumbotron .h1,.jumbotron h1{
+        font-size:63px
+    }
+}
+.thumbnail{
+    display:block;
+    padding:4px;
+    margin-bottom:20px;
+    line-height:1.42857143;
+    background-color:#fff;
+    border:1px solid #ddd;
+    border-radius:4px;
+    -webkit-transition:border .2s ease-in-out;
+    -o-transition:border .2s ease-in-out;
+    transition:border .2s ease-in-out
+}
+.thumbnail a>img,.thumbnail>img{
+    margin-right:auto;
+    margin-left:auto
+}
+a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{
+    border-color:#337ab7
+}
+.thumbnail .caption{
+    padding:9px;
+    color:#333
+}
+.alert{
+    padding:15px;
+    margin-bottom:20px;
+    border:1px solid transparent;
+    border-radius:4px
+}
+.alert h4{
+    margin-top:0;
+    color:inherit
+}
+.alert .alert-link{
+    font-weight:700
+}
+.alert>p,.alert>ul{
+    margin-bottom:0
+}
+.alert>p+p{
+    margin-top:5px
+}
+.alert-dismissable,.alert-dismissible{
+    padding-right:35px
+}
+.alert-dismissable .close,.alert-dismissible .close{
+    position:relative;
+    top:-2px;
+    right:-21px;
+    color:inherit
+}
+.alert-success{
+    color:#3c763d;
+    background-color:#dff0d8;
+    border-color:#d6e9c6
+}
+.alert-success hr{
+    border-top-color:#c9e2b3
+}
+.alert-success .alert-link{
+    color:#2b542c
+}
+.alert-info{
+    color:#31708f;
+    background-color:#d9edf7;
+    border-color:#bce8f1
+}
+.alert-info hr{
+    border-top-color:#a6e1ec
+}
+.alert-info .alert-link{
+    color:#245269
+}
+.alert-warning{
+    color:#8a6d3b;
+    background-color:#fcf8e3;
+    border-color:#faebcc
+}
+.alert-warning hr{
+    border-top-color:#f7e1b5
+}
+.alert-warning .alert-link{
+    color:#66512c
+}
+.alert-danger{
+    color:#a94442;
+    background-color:#f2dede;
+    border-color:#ebccd1
+}
+.alert-danger hr{
+    border-top-color:#e4b9c0
+}
+.alert-danger .alert-link{
+    color:#843534
+}
+@-webkit-keyframes progress-bar-stripes{
+    from{
+        background-position:40px 0
+    }
+    to{
+        background-position:0 0
+    }
+}
+@-o-keyframes progress-bar-stripes{
+    from{
+        background-position:40px 0
+    }
+    to{
+        background-position:0 0
+    }
+}
+@keyframes progress-bar-stripes{
+    from{
+        background-position:40px 0
+    }
+    to{
+        background-position:0 0
+    }
+}
+.progress{
+    height:20px;
+    margin-bottom:20px;
+    overflow:hidden;
+    background-color:#f5f5f5;
+    border-radius:4px;
+    -webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);
+    box-shadow:inset 0 1px 2px rgba(0,0,0,.1)
+}
+.progress-bar{
+    float:left;
+    width:0;
+    height:100%;
+    font-size:12px;
+    line-height:20px;
+    color:#fff;
+    text-align:center;
+    background-color:#337ab7;
+    -webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);
+    box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);
+    -webkit-transition:width .6s ease;
+    -o-transition:width .6s ease;
+    transition:width .6s ease
+}
+.progress-bar-striped,.progress-striped .progress-bar{
+    background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    -webkit-background-size:40px 40px;
+    background-size:40px 40px
+}
+.progress-bar.active,.progress.active .progress-bar{
+    -webkit-animation:progress-bar-stripes 2s linear infinite;
+    -o-animation:progress-bar-stripes 2s linear infinite;
+    animation:progress-bar-stripes 2s linear infinite
+}
+.progress-bar-success{
+    background-color:#5cb85c
+}
+.progress-striped .progress-bar-success{
+    background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+.progress-bar-info{
+    background-color:#5bc0de
+}
+.progress-striped .progress-bar-info{
+    background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+.progress-bar-warning{
+    background-color:#f0ad4e
+}
+.progress-striped .progress-bar-warning{
+    background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+.progress-bar-danger{
+    background-color:#d9534f
+}
+.progress-striped .progress-bar-danger{
+    background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
+    background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
+}
+.media{
+    margin-top:15px
+}
+.media:first-child{
+    margin-top:0
+}
+.media,.media-body{
+    overflow:hidden;
+    zoom:1
+}
+.media-body{
+    width:10000px
+}
+.media-object{
+    display:block
+}
+.media-object.img-thumbnail{
+    max-width:none
+}
+.media-right,.media>.pull-right{
+    padding-left:10px
+}
+.media-left,.media>.pull-left{
+    padding-right:10px
+}
+.media-body,.media-left,.media-right{
+    display:table-cell;
+    vertical-align:top
+}
+.media-middle{
+    vertical-align:middle
+}
+.media-bottom{
+    vertical-align:bottom
+}
+.media-heading{
+    margin-top:0;
+    margin-bottom:5px
+}
+.media-list{
+    padding-left:0;
+    list-style:none
+}
+.list-group{
+    padding-left:0;
+    margin-bottom:20px
+}
+.list-group-item{
+    position:relative;
+    display:block;
+    padding:10px 15px;
+    margin-bottom:-1px;
+    background-color:#fff;
+    border:1px solid #ddd
+}
+.list-group-item:first-child{
+    border-top-left-radius:4px;
+    border-top-right-radius:4px
+}
+.list-group-item:last-child{
+    margin-bottom:0;
+    border-bottom-right-radius:4px;
+    border-bottom-left-radius:4px
+}
+a.list-group-item,button.list-group-item{
+    color:#555
+}
+a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{
+    color:#333
+}
+a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{
+    color:#555;
+    text-decoration:none;
+    background-color:#f5f5f5
+}
+button.list-group-item{
+    width:100%;
+    text-align:left
+}
+.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{
+    color:#777;
+    cursor:not-allowed;
+    background-color:#eee
+}
+.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{
+    color:inherit
+}
+.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{
+    color:#777
+}
+.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{
+    z-index:2;
+    color:#fff;
+    background-color:#337ab7;
+    border-color:#337ab7
+}
+.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{
+    color:inherit
+}
+.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{
+    color:#c7ddef
+}
+.list-group-item-success{
+    color:#3c763d;
+    background-color:#dff0d8
+}
+a.list-group-item-success,button.list-group-item-success{
+    color:#3c763d
+}
+a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{
+    color:inherit
+}
+a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{
+    color:#3c763d;
+    background-color:#d0e9c6
+}
+a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{
+    color:#fff;
+    background-color:#3c763d;
+    border-color:#3c763d
+}
+.list-group-item-info{
+    color:#31708f;
+    background-color:#d9edf7
+}
+a.list-group-item-info,button.list-group-item-info{
+    color:#31708f
+}
+a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{
+    color:inherit
+}
+a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{
+    color:#31708f;
+    background-color:#c4e3f3
+}
+a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{
+    color:#fff;
+    background-color:#31708f;
+    border-color:#31708f
+}
+.list-group-item-warning{
+    color:#8a6d3b;
+    background-color:#fcf8e3
+}
+a.list-group-item-warning,button.list-group-item-warning{
+    color:#8a6d3b
+}
+a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{
+    color:inherit
+}
+a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{
+    color:#8a6d3b;
+    background-color:#faf2cc
+}
+a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{
+    color:#fff;
+    background-color:#8a6d3b;
+    border-color:#8a6d3b
+}
+.list-group-item-danger{
+    color:#a94442;
+    background-color:#f2dede
+}
+a.list-group-item-danger,button.list-group-item-danger{
+    color:#a94442
+}
+a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{
+    color:inherit
+}
+a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{
+    color:#a94442;
+    background-color:#ebcccc
+}
+a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{
+    color:#fff;
+    background-color:#a94442;
+    border-color:#a94442
+}
+.list-group-item-heading{
+    margin-top:0;
+    margin-bottom:5px
+}
+.list-group-item-text{
+    margin-bottom:0;
+    line-height:1.3
+}
+.panel{
+    margin-bottom:20px;
+    background-color:#fff;
+    border:1px solid transparent;
+    border-radius:4px;
+    -webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);
+    box-shadow:0 1px 1px rgba(0,0,0,.05)
+}
+.panel-body{
+    padding:15px
+}
+.panel-heading{
+    padding:10px 15px;
+    border-bottom:1px solid transparent;
+    border-top-left-radius:3px;
+    border-top-right-radius:3px
+}
+.panel-heading>.dropdown .dropdown-toggle{
+    color:inherit
+}
+.panel-title{
+    margin-top:0;
+    margin-bottom:0;
+    font-size:16px;
+    color:inherit
+}
+.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{
+    color:inherit
+}
+.panel-footer{
+    padding:10px 15px;
+    background-color:#f5f5f5;
+    border-top:1px solid #ddd;
+    border-bottom-right-radius:3px;
+    border-bottom-left-radius:3px
+}
+.panel>.list-group,.panel>.panel-collapse>.list-group{
+    margin-bottom:0
+}
+.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{
+    border-width:1px 0;
+    border-radius:0
+}
+.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{
+    border-top:0;
+    border-top-left-radius:3px;
+    border-top-right-radius:3px
+}
+.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{
+    border-bottom:0;
+    border-bottom-right-radius:3px;
+    border-bottom-left-radius:3px
+}
+.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{
+    border-top-left-radius:0;
+    border-top-right-radius:0
+}
+.panel-heading+.list-group .list-group-item:first-child{
+    border-top-width:0
+}
+.list-group+.panel-footer{
+    border-top-width:0
+}
+.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{
+    margin-bottom:0
+}
+.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{
+    padding-right:15px;
+    padding-left:15px
+}
+.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{
+    border-top-left-radius:3px;
+    border-top-right-radius:3px
+}
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{
+    border-top-left-radius:3px;
+    border-top-right-radius:3px
+}
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{
+    border-top-left-radius:3px
+}
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{
+    border-top-right-radius:3px
+}
+.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{
+    border-bottom-right-radius:3px;
+    border-bottom-left-radius:3px
+}
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{
+    border-bottom-right-radius:3px;
+    border-bottom-left-radius:3px
+}
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{
+    border-bottom-left-radius:3px
+}
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{
+    border-bottom-right-radius:3px
+}
+.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{
+    border-top:1px solid #ddd
+}
+.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{
+    border-top:0
+}
+.panel>.table-bordered,.panel>.table-responsive>.table-bordered{
+    border:0
+}
+.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{
+    border-left:0
+}
+.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{
+    border-right:0
+}
+.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{
+    border-bottom:0
+}
+.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{
+    border-bottom:0
+}
+.panel>.table-responsive{
+    margin-bottom:0;
+    border:0
+}
+.panel-group{
+    margin-bottom:20px
+}
+.panel-group .panel{
+    margin-bottom:0;
+    border-radius:4px
+}
+.panel-group .panel+.panel{
+    margin-top:5px
+}
+.panel-group .panel-heading{
+    border-bottom:0
+}
+.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{
+    border-top:1px solid #ddd
+}
+.panel-group .panel-footer{
+    border-top:0
+}
+.panel-group .panel-footer+.panel-collapse .panel-body{
+    border-bottom:1px solid #ddd
+}
+.panel-default{
+    border-color:#ddd
+}
+.panel-default>.panel-heading{
+    color:#333;
+    background-color:#f5f5f5;
+    border-color:#ddd
+}
+.panel-default>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#ddd
+}
+.panel-default>.panel-heading .badge{
+    color:#f5f5f5;
+    background-color:#333
+}
+.panel-default>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#ddd
+}
+.panel-primary{
+    border-color:#337ab7
+}
+.panel-primary>.panel-heading{
+    color:#fff;
+    background-color:#337ab7;
+    border-color:#337ab7
+}
+.panel-primary>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#337ab7
+}
+.panel-primary>.panel-heading .badge{
+    color:#337ab7;
+    background-color:#fff
+}
+.panel-primary>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#337ab7
+}
+.panel-success{
+    border-color:#d6e9c6
+}
+.panel-success>.panel-heading{
+    color:#3c763d;
+    background-color:#dff0d8;
+    border-color:#d6e9c6
+}
+.panel-success>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#d6e9c6
+}
+.panel-success>.panel-heading .badge{
+    color:#dff0d8;
+    background-color:#3c763d
+}
+.panel-success>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#d6e9c6
+}
+.panel-info{
+    border-color:#bce8f1
+}
+.panel-info>.panel-heading{
+    color:#31708f;
+    background-color:#d9edf7;
+    border-color:#bce8f1
+}
+.panel-info>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#bce8f1
+}
+.panel-info>.panel-heading .badge{
+    color:#d9edf7;
+    background-color:#31708f
+}
+.panel-info>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#bce8f1
+}
+.panel-warning{
+    border-color:#faebcc
+}
+.panel-warning>.panel-heading{
+    color:#8a6d3b;
+    background-color:#fcf8e3;
+    border-color:#faebcc
+}
+.panel-warning>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#faebcc
+}
+.panel-warning>.panel-heading .badge{
+    color:#fcf8e3;
+    background-color:#8a6d3b
+}
+.panel-warning>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#faebcc
+}
+.panel-danger{
+    border-color:#ebccd1
+}
+.panel-danger>.panel-heading{
+    color:#a94442;
+    background-color:#f2dede;
+    border-color:#ebccd1
+}
+.panel-danger>.panel-heading+.panel-collapse>.panel-body{
+    border-top-color:#ebccd1
+}
+.panel-danger>.panel-heading .badge{
+    color:#f2dede;
+    background-color:#a94442
+}
+.panel-danger>.panel-footer+.panel-collapse>.panel-body{
+    border-bottom-color:#ebccd1
+}
+.embed-responsive{
+    position:relative;
+    display:block;
+    height:0;
+    padding:0;
+    overflow:hidden
+}
+.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{
+    position:absolute;
+    top:0;
+    bottom:0;
+    left:0;
+    width:100%;
+    height:100%;
+    border:0
+}
+.embed-responsive-16by9{
+    padding-bottom:56.25%
+}
+.embed-responsive-4by3{
+    padding-bottom:75%
+}
+.well{
+    min-height:20px;
+    padding:19px;
+    margin-bottom:20px;
+    background-color:#f5f5f5;
+    border:1px solid #e3e3e3;
+    border-radius:4px;
+    -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);
+    box-shadow:inset 0 1px 1px rgba(0,0,0,.05)
+}
+.well blockquote{
+    border-color:#ddd;
+    border-color:rgba(0,0,0,.15)
+}
+.well-lg{
+    padding:24px;
+    border-radius:6px
+}
+.well-sm{
+    padding:9px;
+    border-radius:3px
+}
+.close{
+    float:right;
+    font-size:21px;
+    font-weight:700;
+    line-height:1;
+    color:#000;
+    text-shadow:0 1px 0 #fff;
+    filter:alpha(opacity=20);
+    opacity:.2
+}
+.close:focus,.close:hover{
+    color:#000;
+    text-decoration:none;
+    cursor:pointer;
+    filter:alpha(opacity=50);
+    opacity:.5
+}
+button.close{
+    -webkit-appearance:none;
+    padding:0;
+    cursor:pointer;
+    background:0 0;
+    border:0
+}
+.modal-open{
+    overflow:hidden
+}
+.modal{
+    position:fixed;
+    top:0;
+    right:0;
+    bottom:0;
+    left:0;
+    z-index:1050;
+    display:none;
+    overflow:hidden;
+    -webkit-overflow-scrolling:touch;
+    outline:0
+}
+.modal.fade .modal-dialog{
+    -webkit-transition:-webkit-transform .3s ease-out;
+    -o-transition:-o-transform .3s ease-out;
+    transition:transform .3s ease-out;
+    -webkit-transform:translate(0,-25%);
+    -ms-transform:translate(0,-25%);
+    -o-transform:translate(0,-25%);
+    transform:translate(0,-25%)
+}
+.modal.in .modal-dialog{
+    -webkit-transform:translate(0,0);
+    -ms-transform:translate(0,0);
+    -o-transform:translate(0,0);
+    transform:translate(0,0)
+}
+.modal-open .modal{
+    overflow-x:hidden;
+    overflow-y:auto
+}
+.modal-dialog{
+    position:relative;
+    width:auto;
+    margin:10px
+}
+.modal-content{
+    position:relative;
+    background-color:#fff;
+    -webkit-background-clip:padding-box;
+    background-clip:padding-box;
+    border:1px solid #999;
+    border:1px solid rgba(0,0,0,.2);
+    border-radius:6px;
+    outline:0;
+    -webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);
+    box-shadow:0 3px 9px rgba(0,0,0,.5)
+}
+.modal-backdrop{
+    position:fixed;
+    top:0;
+    right:0;
+    bottom:0;
+    left:0;
+    z-index:1040;
+    background-color:#000
+}
+.modal-backdrop.fade{
+    filter:alpha(opacity=0);
+    opacity:0
+}
+.modal-backdrop.in{
+    filter:alpha(opacity=50);
+    opacity:.5
+}
+.modal-header{
+    padding:15px;
+    border-bottom:1px solid #e5e5e5
+}
+.modal-header .close{
+    margin-top:-2px
+}
+.modal-title{
+    margin:0;
+    line-height:1.42857143
+}
+.modal-body{
+    position:relative;
+    padding:15px
+}
+.modal-footer{
+    padding:15px;
+    text-align:right;
+    border-top:1px solid #e5e5e5
+}
+.modal-footer .btn+.btn{
+    margin-bottom:0;
+    margin-left:5px
+}
+.modal-footer .btn-group .btn+.btn{
+    margin-left:-1px
+}
+.modal-footer .btn-block+.btn-block{
+    margin-left:0
+}
+.modal-scrollbar-measure{
+    position:absolute;
+    top:-9999px;
+    width:50px;
+    height:50px;
+    overflow:scroll
+}
+@media (min-width:768px){
+    .modal-dialog{
+        width:600px;
+        margin:30px auto
+    }
+    .modal-content{
+        -webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);
+        box-shadow:0 5px 15px rgba(0,0,0,.5)
+    }
+    .modal-sm{
+        width:300px
+    }
+}
+@media (min-width:992px){
+    .modal-lg{
+        width:900px
+    }
+}
+.tooltip{
+    position:absolute;
+    z-index:1070;
+    display:block;
+    font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
+    font-size:12px;
+    font-style:normal;
+    font-weight:400;
+    line-height:1.42857143;
+    text-align:left;
+    text-align:start;
+    text-decoration:none;
+    text-shadow:none;
+    text-transform:none;
+    letter-spacing:normal;
+    word-break:normal;
+    word-spacing:normal;
+    word-wrap:normal;
+    white-space:normal;
+    filter:alpha(opacity=0);
+    opacity:0;
+    line-break:auto
+}
+.tooltip.in{
+    filter:alpha(opacity=90);
+    opacity:.9
+}
+.tooltip.top{
+    padding:5px 0;
+    margin-top:-3px
+}
+.tooltip.right{
+    padding:0 5px;
+    margin-left:3px
+}
+.tooltip.bottom{
+    padding:5px 0;
+    margin-top:3px
+}
+.tooltip.left{
+    padding:0 5px;
+    margin-left:-3px
+}
+.tooltip-inner{
+    max-width:200px;
+    padding:3px 8px;
+    color:#fff;
+    text-align:center;
+    background-color:#000;
+    border-radius:4px
+}
+.tooltip-arrow{
+    position:absolute;
+    width:0;
+    height:0;
+    border-color:transparent;
+    border-style:solid
+}
+.tooltip.top .tooltip-arrow{
+    bottom:0;
+    left:50%;
+    margin-left:-5px;
+    border-width:5px 5px 0;
+    border-top-color:#000
+}
+.tooltip.top-left .tooltip-arrow{
+    right:5px;
+    bottom:0;
+    margin-bottom:-5px;
+    border-width:5px 5px 0;
+    border-top-color:#000
+}
+.tooltip.top-right .tooltip-arrow{
+    bottom:0;
+    left:5px;
+    margin-bottom:-5px;
+    border-width:5px 5px 0;
+    border-top-color:#000
+}
+.tooltip.right .tooltip-arrow{
+    top:50%;
+    left:0;
+    margin-top:-5px;
+    border-width:5px 5px 5px 0;
+    border-right-color:#000
+}
+.tooltip.left .tooltip-arrow{
+    top:50%;
+    right:0;
+    margin-top:-5px;
+    border-width:5px 0 5px 5px;
+    border-left-color:#000
+}
+.tooltip.bottom .tooltip-arrow{
+    top:0;
+    left:50%;
+    margin-left:-5px;
+    border-width:0 5px 5px;
+    border-bottom-color:#000
+}
+.tooltip.bottom-left .tooltip-arrow{
+    top:0;
+    right:5px;
+    margin-top:-5px;
+    border-width:0 5px 5px;
+    border-bottom-color:#000
+}
+.tooltip.bottom-right .tooltip-arrow{
+    top:0;
+    left:5px;
+    margin-top:-5px;
+    border-width:0 5px 5px;
+    border-bottom-color:#000
+}
+.popover{
+    position:absolute;
+    top:0;
+    left:0;
+    z-index:1060;
+    display:none;
+    max-width:276px;
+    padding:1px;
+    font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;
+    font-size:14px;
+    font-style:normal;
+    font-weight:400;
+    line-height:1.42857143;
+    text-align:left;
+    text-align:start;
+    text-decoration:none;
+    text-shadow:none;
+    text-transform:none;
+    letter-spacing:normal;
+    word-break:normal;
+    word-spacing:normal;
+    word-wrap:normal;
+    white-space:normal;
+    background-color:#fff;
+    -webkit-background-clip:padding-box;
+    background-clip:padding-box;
+    border:1px solid #ccc;
+    border:1px solid rgba(0,0,0,.2);
+    border-radius:6px;
+    -webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);
+    box-shadow:0 5px 10px rgba(0,0,0,.2);
+    line-break:auto
+}
+.popover.top{
+    margin-top:-10px
+}
+.popover.right{
+    margin-left:10px
+}
+.popover.bottom{
+    margin-top:10px
+}
+.popover.left{
+    margin-left:-10px
+}
+.popover-title{
+    padding:8px 14px;
+    margin:0;
+    font-size:14px;
+    background-color:#f7f7f7;
+    border-bottom:1px solid #ebebeb;
+    border-radius:5px 5px 0 0
+}
+.popover-content{
+    padding:9px 14px
+}
+.popover>.arrow,.popover>.arrow:after{
+    position:absolute;
+    display:block;
+    width:0;
+    height:0;
+    border-color:transparent;
+    border-style:solid
+}
+.popover>.arrow{
+    border-width:11px
+}
+.popover>.arrow:after{
+    content:"";
+    border-width:10px
+}
+.popover.top>.arrow{
+    bottom:-11px;
+    left:50%;
+    margin-left:-11px;
+    border-top-color:#999;
+    border-top-color:rgba(0,0,0,.25);
+    border-bottom-width:0
+}
+.popover.top>.arrow:after{
+    bottom:1px;
+    margin-left:-10px;
+    content:" ";
+    border-top-color:#fff;
+    border-bottom-width:0
+}
+.popover.right>.arrow{
+    top:50%;
+    left:-11px;
+    margin-top:-11px;
+    border-right-color:#999;
+    border-right-color:rgba(0,0,0,.25);
+    border-left-width:0
+}
+.popover.right>.arrow:after{
+    bottom:-10px;
+    left:1px;
+    content:" ";
+    border-right-color:#fff;
+    border-left-width:0
+}
+.popover.bottom>.arrow{
+    top:-11px;
+    left:50%;
+    margin-left:-11px;
+    border-top-width:0;
+    border-bottom-color:#999;
+    border-bottom-color:rgba(0,0,0,.25)
+}
+.popover.bottom>.arrow:after{
+    top:1px;
+    margin-left:-10px;
+    content:" ";
+    border-top-width:0;
+    border-bottom-color:#fff
+}
+.popover.left>.arrow{
+    top:50%;
+    right:-11px;
+    margin-top:-11px;
+    border-right-width:0;
+    border-left-color:#999;
+    border-left-color:rgba(0,0,0,.25)
+}
+.popover.left>.arrow:after{
+    right:1px;
+    bottom:-10px;
+    content:" ";
+    border-right-width:0;
+    border-left-color:#fff
+}
+.carousel{
+    position:relative
+}
+.carousel-inner{
+    position:relative;
+    width:100%;
+    overflow:hidden
+}
+.carousel-inner>.item{
+    position:relative;
+    display:none;
+    -webkit-transition:.6s ease-in-out left;
+    -o-transition:.6s ease-in-out left;
+    transition:.6s ease-in-out left
+}
+.carousel-inner>.item>a>img,.carousel-inner>.item>img{
+    line-height:1
+}
+@media all and (transform-3d),(-webkit-transform-3d){
+    .carousel-inner>.item{
+        -webkit-transition:-webkit-transform .6s ease-in-out;
+        -o-transition:-o-transform .6s ease-in-out;
+        transition:transform .6s ease-in-out;
+        -webkit-backface-visibility:hidden;
+        backface-visibility:hidden;
+        -webkit-perspective:1000px;
+        perspective:1000px
+    }
+    .carousel-inner>.item.active.right,.carousel-inner>.item.next{
+        left:0;
+        -webkit-transform:translate3d(100%,0,0);
+        transform:translate3d(100%,0,0)
+    }
+    .carousel-inner>.item.active.left,.carousel-inner>.item.prev{
+        left:0;
+        -webkit-transform:translate3d(-100%,0,0);
+        transform:translate3d(-100%,0,0)
+    }
+    .carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{
+        left:0;
+        -webkit-transform:translate3d(0,0,0);
+        transform:translate3d(0,0,0)
+    }
+}
+.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{
+    display:block
+}
+.carousel-inner>.active{
+    left:0
+}
+.carousel-inner>.next,.carousel-inner>.prev{
+    position:absolute;
+    top:0;
+    width:100%
+}
+.carousel-inner>.next{
+    left:100%
+}
+.carousel-inner>.prev{
+    left:-100%
+}
+.carousel-inner>.next.left,.carousel-inner>.prev.right{
+    left:0
+}
+.carousel-inner>.active.left{
+    left:-100%
+}
+.carousel-inner>.active.right{
+    left:100%
+}
+.carousel-control{
+    position:absolute;
+    top:0;
+    bottom:0;
+    left:0;
+    width:15%;
+    font-size:20px;
+    color:#fff;
+    text-align:center;
+    text-shadow:0 1px 2px rgba(0,0,0,.6);
+    background-color:rgba(0,0,0,0);
+    filter:alpha(opacity=50);
+    opacity:.5
+}
+.carousel-control.left{
+    background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
+    background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
+    background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));
+    background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
+    filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
+    background-repeat:repeat-x
+}
+.carousel-control.right{
+    right:0;
+    left:auto;
+    background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
+    background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
+    background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));
+    background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
+    filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
+    background-repeat:repeat-x
+}
+.carousel-control:focus,.carousel-control:hover{
+    color:#fff;
+    text-decoration:none;
+    filter:alpha(opacity=90);
+    outline:0;
+    opacity:.9
+}
+.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{
+    position:absolute;
+    top:50%;
+    z-index:5;
+    display:inline-block;
+    margin-top:-10px
+}
+.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{
+    left:50%;
+    margin-left:-10px
+}
+.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{
+    right:50%;
+    margin-right:-10px
+}
+.carousel-control .icon-next,.carousel-control .icon-prev{
+    width:20px;
+    height:20px;
+    font-family:serif;
+    line-height:1
+}
+.carousel-control .icon-prev:before{
+    content:'\2039'
+}
+.carousel-control .icon-next:before{
+    content:'\203a'
+}
+.carousel-indicators{
+    position:absolute;
+    bottom:10px;
+    left:50%;
+    z-index:15;
+    width:60%;
+    padding-left:0;
+    margin-left:-30%;
+    text-align:center;
+    list-style:none
+}
+.carousel-indicators li{
+    display:inline-block;
+    width:10px;
+    height:10px;
+    margin:1px;
+    text-indent:-999px;
+    cursor:pointer;
+    background-color:#000\9;
+    background-color:rgba(0,0,0,0);
+    border:1px solid #fff;
+    border-radius:10px
+}
+.carousel-indicators .active{
+    width:12px;
+    height:12px;
+    margin:0;
+    background-color:#fff
+}
+.carousel-caption{
+    position:absolute;
+    right:15%;
+    bottom:20px;
+    left:15%;
+    z-index:10;
+    padding-top:20px;
+    padding-bottom:20px;
+    color:#fff;
+    text-align:center;
+    text-shadow:0 1px 2px rgba(0,0,0,.6)
+}
+.carousel-caption .btn{
+    text-shadow:none
+}
+@media screen and (min-width:768px){
+    .carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{
+        width:30px;
+        height:30px;
+        margin-top:-10px;
+        font-size:30px
+    }
+    .carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{
+        margin-left:-10px
+    }
+    .carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{
+        margin-right:-10px
+    }
+    .carousel-caption{
+        right:20%;
+        left:20%;
+        padding-bottom:30px
+    }
+    .carousel-indicators{
+        bottom:20px
+    }
+}
+.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{
+    display:table;
+    content:" "
+}
+.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{
+    clear:both
+}
+.center-block{
+    display:block;
+    margin-right:auto;
+    margin-left:auto
+}
+.pull-right{
+    float:right!important
+}
+.pull-left{
+    float:left!important
+}
+.hide{
+    display:none!important
+}
+.show{
+    display:block!important
+}
+.invisible{
+    visibility:hidden
+}
+.text-hide{
+    font:0/0 a;
+    color:transparent;
+    text-shadow:none;
+    background-color:transparent;
+    border:0
+}
+.hidden{
+    display:none!important
+}
+.affix{
+    position:fixed
+}
+@-ms-viewport{
+    width:device-width
+}
+.visible-lg,.visible-md,.visible-sm,.visible-xs{
+    display:none!important
+}
+.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{
+    display:none!important
+}
+@media (max-width:767px){
+    .visible-xs{
+        display:block!important
+    }
+    table.visible-xs{
+        display:table!important
+    }
+    tr.visible-xs{
+        display:table-row!important
+    }
+    td.visible-xs,th.visible-xs{
+        display:table-cell!important
+    }
+}
+@media (max-width:767px){
+    .visible-xs-block{
+        display:block!important
+    }
+}
+@media (max-width:767px){
+    .visible-xs-inline{
+        display:inline!important
+    }
+}
+@media (max-width:767px){
+    .visible-xs-inline-block{
+        display:inline-block!important
+    }
+}
+@media (min-width:768px) and (max-width:991px){
+    .visible-sm{
+        display:block!important
+    }
+    table.visible-sm{
+        display:table!important
+    }
+    tr.visible-sm{
+        display:table-row!important
+    }
+    td.visible-sm,th.visible-sm{
+        display:table-cell!important
+    }
+}
+@media (min-width:768px) and (max-width:991px){
+    .visible-sm-block{
+        display:block!important
+    }
+}
+@media (min-width:768px) and (max-width:991px){
+    .visible-sm-inline{
+        display:inline!important
+    }
+}
+@media (min-width:768px) and (max-width:991px){
+    .visible-sm-inline-block{
+        display:inline-block!important
+    }
+}
+@media (min-width:992px) and (max-width:1199px){
+    .visible-md{
+        display:block!important
+    }
+    table.visible-md{
+        display:table!important
+    }
+    tr.visible-md{
+        display:table-row!important
+    }
+    td.visible-md,th.visible-md{
+        display:table-cell!important
+    }
+}
+@media (min-width:992px) and (max-width:1199px){
+    .visible-md-block{
+        display:block!important
+    }
+}
+@media (min-width:992px) and (max-width:1199px){
+    .visible-md-inline{
+        display:inline!important
+    }
+}
+@media (min-width:992px) and (max-width:1199px){
+    .visible-md-inline-block{
+        display:inline-block!important
+    }
+}
+@media (min-width:1200px){
+    .visible-lg{
+        display:block!important
+    }
+    table.visible-lg{
+        display:table!important
+    }
+    tr.visible-lg{
+        display:table-row!important
+    }
+    td.visible-lg,th.visible-lg{
+        display:table-cell!important
+    }
+}
+@media (min-width:1200px){
+    .visible-lg-block{
+        display:block!important
+    }
+}
+@media (min-width:1200px){
+    .visible-lg-inline{
+        display:inline!important
+    }
+}
+@media (min-width:1200px){
+    .visible-lg-inline-block{
+        display:inline-block!important
+    }
+}
+@media (max-width:767px){
+    .hidden-xs{
+        display:none!important
+    }
+}
+@media (min-width:768px) and (max-width:991px){
+    .hidden-sm{
+        display:none!important
+    }
+}
+@media (min-width:992px) and (max-width:1199px){
+    .hidden-md{
+        display:none!important
+    }
+}
+@media (min-width:1200px){
+    .hidden-lg{
+        display:none!important
+    }
+}
+.visible-print{
+    display:none!important
+}
+@media print{
+    .visible-print{
+        display:block!important
+    }
+    table.visible-print{
+        display:table!important
+    }
+    tr.visible-print{
+        display:table-row!important
+    }
+    td.visible-print,th.visible-print{
+        display:table-cell!important
+    }
+}
+.visible-print-block{
+    display:none!important
+}
+@media print{
+    .visible-print-block{
+        display:block!important
+    }
+}
+.visible-print-inline{
+    display:none!important
+}
+@media print{
+    .visible-print-inline{
+        display:inline!important
+    }
+}
+.visible-print-inline-block{
+    display:none!important
+}
+@media print{
+    .visible-print-inline-block{
+        display:inline-block!important
+    }
+}
+@media print{
+    .hidden-print{
+        display:none!important
+    }
+}
+/*# sourceMappingURL=bootstrap.min.css.map */
+ 
diff --git a/themes/triangles/client/src/css/01-main.css b/themes/triangles/client/src/css/01-main.css
new file mode 100644
index 00000000..347c0b81
--- /dev/null
+++ b/themes/triangles/client/src/css/01-main.css
@@ -0,0 +1,77 @@
+body {
+  background-image: url("/img/LargeTriangles.svg");
+  /*background-image: url("//*img//*RandomizedPattern.svg");*/
+  /*background-image: url("//*img//*background.svg");*/  
+  /*background-color:#000000;*/
+}
+canvas{
+  position:absolute;
+  top:0;
+  left:0;
+}
+.authelia-brand {
+  font-weight: bold;
+  font-style: italic;
+  color: #ffffff
+}
+.poweredby-block {
+  margin: 0px 30px;
+  margin-top: 10px;
+  padding-top: 15px;
+  border-top: 1px solid rgba(0, 0, 0, 0.15);
+
+}
+.poweredby {
+  font-size: 0.7em;
+  color: white;
+}
+/* notifications */
+.notification {
+  padding: 10px;
+  margin: 15px 0px;
+  border-radius: 6px;
+  display: none;
+  position: absolute;
+}
+.notification img {
+  width: 24px;
+  margin-right: 10px;
+}
+.notification i,
+.notification span {
+  display:table-cell;
+  vertical-align:middle;
+}
+.info {
+  border: 1px solid #9cb1ff;
+  background-color: rgb(192, 220, 255);
+}
+.success {
+  border: 1px solid #65ec7c;
+  background-color: rgb(163, 255, 157);
+}
+.error {
+  border: 1px solid #ffa3a3;
+  background-color: rgb(255, 175, 175);
+}
+.warning {
+  border: 1px solid #ffd743;
+  background-color: rgb(255, 230, 143);
+}
+.bottom-right-links {
+  text-align: right;
+  margin-top: 10px;
+  font-size: 0.8em;
+  color: white;
+}
+.header {
+  background-color: #000000;
+  color: white;
+  margin: 0px;
+}
+.body {
+  padding: 10px;
+}
+h1 {
+  font-size: 25px;
+}
diff --git a/themes/triangles/client/src/css/02-login.css b/themes/triangles/client/src/css/02-login.css
new file mode 100644
index 00000000..a6984267
--- /dev/null
+++ b/themes/triangles/client/src/css/02-login.css
@@ -0,0 +1,136 @@
+.form-signin
+{
+    margin: 0 auto;
+}
+
+.form-signin .form-signin-heading, .form-signin .checkbox
+{
+    margin-bottom: 10px;
+}
+
+.form-signin .checkbox
+{
+    font-weight: normal;
+}
+
+.form-signin .form-control
+{
+    position: relative;
+    font-size: 16px;
+    height: auto;
+    padding: 10px;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+}
+.form-signin .form-control:focus
+{
+    z-index: 2;
+}
+.form-signin input[type="text"]
+{
+    margin-bottom: -1px;
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+}
+.form-signin input[type="password"]
+{
+    /* margin-bottom: 10px; */
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
+}
+.account-wall
+{
+    border: 1px solid #000;
+    margin-top: 20px;
+    padding-bottom: 20px;
+    background-color: #000000;
+    -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 1);
+    -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 1);
+    box-shadow: 0px 2px 2px rgba(0, 0, 0, 1);
+}
+.account-wall h1
+{
+    margin-bottom: 15px;
+    margin-top: 15px;
+    font-weight: 800;
+    display: block;
+    text-align: center;
+}
+.account-wall h3
+{
+    display: block;
+    text-align: center;
+}
+.account-wall p 
+{
+    text-align: center;
+    margin: 10px;
+    color: white;
+}
+.account-wall .form-inputs 
+{
+    margin-bottom: 10px;
+    border-color: #b20c0c;
+}
+.account-wall hr {
+    border-color: #c5c5c5;
+}
+
+.header-img
+{
+    width: 96px;
+    height: 96px;
+    margin: 0 auto 10px;
+    display: block;
+    -moz-border-radius: 50%;
+    -webkit-border-radius: 50%;
+    border-radius: 50%;
+}
+
+.link
+{
+    margin-top: 10px;
+    color: white;
+}
+
+.btn-primary.totp
+{
+    background-color: rgb(102, 135, 162);
+}
+
+.btn-primary.u2f
+{
+    background-color: rgb(83, 149, 204);
+}
+
+.u2f-token {
+    text-align: center;
+}
+
+.u2f-token img {
+    width: 70px;
+}
+
+.keep-me-logged-in {
+    margin-top: 10px;
+    font-size: 0.8em;
+    color: white;
+}
+
+.keep-me-logged-in input[type=checkbox] {
+    transform: scale(0.8);
+    margin: 0;
+    margin-right: 4px;
+}
+
+.keep-me-logged-in label {
+    font-weight: 300;
+}
+
+.keep-me-logged-in input,
+.keep-me-logged-in label {
+    display: inline-block;
+    margin-bottom: 0; /* I added this after I posted my reply */
+    vertical-align: middle; /* Fixes any weird issues in Firefox and IE */
+}
diff --git a/themes/triangles/client/src/css/03-errors.css b/themes/triangles/client/src/css/03-errors.css
new file mode 100644
index 00000000..e9f97f33
--- /dev/null
+++ b/themes/triangles/client/src/css/03-errors.css
@@ -0,0 +1,12 @@
+
+.error-401 .header-img {
+    border-radius: 0%;
+}
+
+.error-403 .header-img {
+    border-radius: 0%;
+}
+
+.error-404 .header-img {
+    border-radius: 0%;
+}
\ No newline at end of file
diff --git a/themes/triangles/client/src/css/03-password-reset-form.css b/themes/triangles/client/src/css/03-password-reset-form.css
new file mode 100644
index 00000000..34066bc2
--- /dev/null
+++ b/themes/triangles/client/src/css/03-password-reset-form.css
@@ -0,0 +1,4 @@
+
+.password-reset-form .header-img {
+    border-radius: 0%;
+}
diff --git a/themes/triangles/client/src/css/03-password-reset-request.css b/themes/triangles/client/src/css/03-password-reset-request.css
new file mode 100644
index 00000000..1a2ad4df
--- /dev/null
+++ b/themes/triangles/client/src/css/03-password-reset-request.css
@@ -0,0 +1,4 @@
+
+.password-reset-request .header-img {
+    border-radius: 0%;
+}
diff --git a/themes/triangles/client/src/css/03-totp-register.css b/themes/triangles/client/src/css/03-totp-register.css
new file mode 100644
index 00000000..cb76720a
--- /dev/null
+++ b/themes/triangles/client/src/css/03-totp-register.css
@@ -0,0 +1,22 @@
+.totp-register #secret {
+  background-color: white;
+  font-size: 0.9em;
+  font-weight: bold;
+  padding: 5px;
+  border: 1px solid #c7c7c7;
+  word-wrap: break-word;
+}
+.totp-register #qrcode img {
+  margin: 10px auto;
+}
+.totp-register .need-google-authenticator {
+  text-align: center;
+  margin-top: 20px;
+}
+.totp-register .store-badges {
+  margin-top: 5px;
+}
+.totp-register .store-badge {
+  width: 110px;
+  height: 30px;
+}
\ No newline at end of file
diff --git a/themes/triangles/client/src/css/03-u2f-register.css b/themes/triangles/client/src/css/03-u2f-register.css
new file mode 100644
index 00000000..e54cddf8
--- /dev/null
+++ b/themes/triangles/client/src/css/03-u2f-register.css
@@ -0,0 +1,5 @@
+
+.u2f-register img {
+    display: block;
+    margin: 20px auto;
+}
\ No newline at end of file
diff --git a/themes/triangles/client/src/img/LargeTriangles.svg b/themes/triangles/client/src/img/LargeTriangles.svg
new file mode 100644
index 00000000..0988bcb3
--- /dev/null
+++ b/themes/triangles/client/src/img/LargeTriangles.svg
@@ -0,0 +1 @@
+<svg xmlns='http://www.w3.org/2000/svg' width='300' height='250' viewBox='0 0 1080 900'><rect fill='#000000' width='1080' height='900'/><g fill-opacity='0.16'><polygon fill='#444' points='90 150 0 300 180 300'/><polygon points='90 150 180 0 0 0'/><polygon fill='#AAA' points='270 150 360 0 180 0'/><polygon fill='#DDD' points='450 150 360 300 540 300'/><polygon fill='#999' points='450 150 540 0 360 0'/><polygon points='630 150 540 300 720 300'/><polygon fill='#DDD' points='630 150 720 0 540 0'/><polygon fill='#444' points='810 150 720 300 900 300'/><polygon fill='#FFF' points='810 150 900 0 720 0'/><polygon fill='#DDD' points='990 150 900 300 1080 300'/><polygon fill='#444' points='990 150 1080 0 900 0'/><polygon fill='#DDD' points='90 450 0 600 180 600'/><polygon points='90 450 180 300 0 300'/><polygon fill='#666' points='270 450 180 600 360 600'/><polygon fill='#AAA' points='270 450 360 300 180 300'/><polygon fill='#DDD' points='450 450 360 600 540 600'/><polygon fill='#999' points='450 450 540 300 360 300'/><polygon fill='#999' points='630 450 540 600 720 600'/><polygon fill='#FFF' points='630 450 720 300 540 300'/><polygon points='810 450 720 600 900 600'/><polygon fill='#DDD' points='810 450 900 300 720 300'/><polygon fill='#AAA' points='990 450 900 600 1080 600'/><polygon fill='#444' points='990 450 1080 300 900 300'/><polygon fill='#222' points='90 750 0 900 180 900'/><polygon points='270 750 180 900 360 900'/><polygon fill='#DDD' points='270 750 360 600 180 600'/><polygon points='450 750 540 600 360 600'/><polygon points='630 750 540 900 720 900'/><polygon fill='#444' points='630 750 720 600 540 600'/><polygon fill='#AAA' points='810 750 720 900 900 900'/><polygon fill='#666' points='810 750 900 600 720 600'/><polygon fill='#999' points='990 750 900 900 1080 900'/><polygon fill='#999' points='180 0 90 150 270 150'/><polygon fill='#444' points='360 0 270 150 450 150'/><polygon fill='#FFF' points='540 0 450 150 630 150'/><polygon points='900 0 810 150 990 150'/><polygon fill='#222' points='0 300 -90 450 90 450'/><polygon fill='#FFF' points='0 300 90 150 -90 150'/><polygon fill='#FFF' points='180 300 90 450 270 450'/><polygon fill='#666' points='180 300 270 150 90 150'/><polygon fill='#222' points='360 300 270 450 450 450'/><polygon fill='#FFF' points='360 300 450 150 270 150'/><polygon fill='#444' points='540 300 450 450 630 450'/><polygon fill='#222' points='540 300 630 150 450 150'/><polygon fill='#AAA' points='720 300 630 450 810 450'/><polygon fill='#666' points='720 300 810 150 630 150'/><polygon fill='#FFF' points='900 300 810 450 990 450'/><polygon fill='#999' points='900 300 990 150 810 150'/><polygon points='0 600 -90 750 90 750'/><polygon fill='#666' points='0 600 90 450 -90 450'/><polygon fill='#AAA' points='180 600 90 750 270 750'/><polygon fill='#444' points='180 600 270 450 90 450'/><polygon fill='#444' points='360 600 270 750 450 750'/><polygon fill='#999' points='360 600 450 450 270 450'/><polygon fill='#666' points='540 600 630 450 450 450'/><polygon fill='#222' points='720 600 630 750 810 750'/><polygon fill='#FFF' points='900 600 810 750 990 750'/><polygon fill='#222' points='900 600 990 450 810 450'/><polygon fill='#DDD' points='0 900 90 750 -90 750'/><polygon fill='#444' points='180 900 270 750 90 750'/><polygon fill='#FFF' points='360 900 450 750 270 750'/><polygon fill='#AAA' points='540 900 630 750 450 750'/><polygon fill='#FFF' points='720 900 810 750 630 750'/><polygon fill='#222' points='900 900 990 750 810 750'/><polygon fill='#222' points='1080 300 990 450 1170 450'/><polygon fill='#FFF' points='1080 300 1170 150 990 150'/><polygon points='1080 600 990 750 1170 750'/><polygon fill='#666' points='1080 600 1170 450 990 450'/><polygon fill='#DDD' points='1080 900 1170 750 990 750'/></g></svg>
\ No newline at end of file
diff --git a/themes/triangles/client/src/img/background.jpg b/themes/triangles/client/src/img/background.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..974ea273fa87adccec4b4433fc4d097c04ae4c31
GIT binary patch
literal 587
zcmb7<L2d#u3`M__34}>za3BG;m3jxdn?<FO;0Ua`=skLeO1)6UA)wuGli(4e5-l+5
zk2kTNzrXV?AHeH&)q;qyk%#<^XK1Cm5*1R$8dDUe91aK8m)^V5xvHLURfpO+o^hDQ
zl#(CM7qhsS#1uOd(lS$+kujrKxhno!`4hq76;GN1R3IHFZ;>P_E@h=1|8$YR;59+9
zW`I5}XRiI4&WC@yzle_&cQR_(0pU8Ji5j{Gs2||wia~Q)aTB2CVR=3aT5jO?)niY+
P^@QfW&?n$dCIR+GQO8ew

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/icon.png b/themes/triangles/client/src/img/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..040d10c1ab5feaf6720fa8e8c0a0aa336402894e
GIT binary patch
literal 1461
zcmV;m1xosfP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800006VoOIv0000h
z08pkdLy7<Z010qNS#tmYE+YT{E+YYWr9XB6000McNliru;s_oG8!(+tmiGVv02y>e
zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00kXML_t(o!_`+?Y*bYgUHhE-
zn3=ZI7qzh{%0mzcVu-|44KcBlMg%4JNF)*ye@ry-m;Ooo@C(p{@FNm6K%#tzfV8Dp
z(gHC?u?eAyEkY@6v9v&$K4#|5J!kuI=XL2!XD|}uPVUd$_w2p)Ui)z_@E;5PlS6Y?
zzD$W&;<c#bG`q|<OHK2`(Z1F*_Z`5B=eE$u=<U^0j`K;>aqBgCCWIgr3<ka|x8+B}
z=a2UPs{j_R*hn!>%#B17TU=LfaFniBfPfGHk@o-q0}>pSg3elo_&jXcox?pxuirC(
z+64_w<1wcpt~Ct9s1v|4#r%X~rnEyMkPy>M!hxWQY!F}#Xodt_W_^yC&$ChylYpwS
z&=(^YTQB4VP`#+(neoZ!X+^?|Fo&2@Mx>OVR#uekyFNbs3W#>4vjG6i{zq#-D?lmO
zgwb%&er)to`<;|MBis1xiRpQoq=1lspman6NDy#26twqNR#fgA_~q+c*8$*>20$i(
zX43)A%7y?m0U*TPM9r<qXcd6m05r&RoHm4m;gEgAmh$xI#g^d-02A2<SOZKs0Fakk
zCV;f}N;04Ylv2Ko8hJ~C0f4tf`c7QSTS2Kbrw{nRCq)1PUM6U!-pFG)X}mxQr@rxh
z%NZxx&ItDaAiIbP3TFUA=k9FE-|@ywV|M{5Mwq>NfXHy+Yt0<CGX!NMV4F_bX_3Cu
z6leytj)3)gF#nvPg|ic(!y#Mc1rUoTx(oSxaOq1;waYiG^KtBDU<#}^ps%F`%pZG!
z@ZDuK7P-_uke7mR$W{}vgl0hG;;m*$)0<=C;|CNG#6!=2pfhQn0(c{?w0B`))3ady
z%0Cl4FjCNznvH_Um5u=-jHPHOD-ABr*1N8|IHB3gHn0o87637TH>3Y#2NpCw4d56E
zr8CNk8ARxwwFZ)MCdHT&Pdu5eC<zAkNFjQ&&OQagySVy88|F8z0_Zr1DnP>DZpn@^
zg!ri-09%OTsSF5HzMiccym0J>)_N^~%UQkOi}{T!LDU98Rq07el%z1z(&3<exFCS?
zva(J?c=IzJPpo=))%qG=(T|)R0OJKP_a}$WG7#DzXpR@T$gvX`0V#(p+v+a}psJ?)
z&!BCd02o@sm>7?5ok7xHr~3h{1!EAv9uU=n1bkaDm3plKtq}^CpAB|@>l6g=Th{?L
zO><|;%(Uw^S3mw@#S9R`U6(-E0l-S{0N(|L(*w#PM1vt?cfmoFzLeC?=_s+x9)RJv
zx_T-a+gbFrCOycqq7T~N44pf2vuFUnckNe}Et`c$GGYn$Em5~-bAIoXqv}29Cef4~
z2yM@YUF1_#SCxNnnX)rE+6@p6Qr(*6e&3X1gC-+a8c+&BL+%*qK6K@t0rdTNNQVOU
z`=%5Qz|c$<11C_oX0Z=|Z|P*x4y9laU$!iJR{F$j%3FW5_5>uoM=4&3>Oj<9vSgh%
zpy=+qV9L1*!8#Ez<Qro>-`$zbhb**wobmo&McKSC1NF&>%U#7=0}m|ebs1q0HD!v9
zy$h(TT*3Ty{Ax#AA^WnJjZ3BTP6{EanRzux5Qt1y^Q$1#gAm97P#~o>v3^Gnb?z_5
zc2NL$a)2xAmbJ20*BIQu3@cgwp7UhUS%QW_rq~p}dg}0f`!}4kbp4`eG`2hHsz#kW
zZxFdbQ|=801KV!&9J_Phe@-7=xryR#eBI=9yqTE?O9J-xk&7+8_&>_uokeL63{>@u
P00000NkvXXu0mjfXm6c7

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/mail.png b/themes/triangles/client/src/img/mail.png
new file mode 100644
index 0000000000000000000000000000000000000000..834bfce9107a94be10da4c011fb78e37a192888f
GIT binary patch
literal 3545
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
Jtx8RN)PI?rK$`#n

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/matrix_circle_128x128.png b/themes/triangles/client/src/img/matrix_circle_128x128.png
new file mode 100644
index 0000000000000000000000000000000000000000..856e01556b91e0ac25959bd41e9af51888d9ac56
GIT binary patch
literal 35750
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

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/notifications/.directory b/themes/triangles/client/src/img/notifications/.directory
new file mode 100644
index 00000000..7c8b8054
--- /dev/null
+++ b/themes/triangles/client/src/img/notifications/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,57,35
+Version=3
+ViewMode=1
diff --git a/themes/triangles/client/src/img/notifications/error.png b/themes/triangles/client/src/img/notifications/error.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf64d28f7519c816cf348e8a1d39ef0a5d588cf1
GIT binary patch
literal 863
zcmV-l1EBngP)<h;3K|Lk000e1NJLTq001BW001Be0{{R3M5Kzw00003b3#c}2nYz<
z;ZNWI000SaNLh0L007_s007_tqF?^X0000PbVXQnQ*UN;cVTj60C#tHE@^ISb7Ns}
zWiD@WXPfRk8UO$RHBd}cMgRZ*|4B*zU_kU$O7&7l;666mJT>%BO3pDW&@U?SP)W=!
zDc?Ui-aR+*QAzPrN%B%k(lRg6F)Yt8EY2<}@KQ<fQc1=qBgQBs@lr|hQc2i4GsGn#
z(K0Q+As)RR9Pv^~@lr|gQc1QK6}A@^x*Hk08yT_^60;K$tqu;Y4-c&n5Uvjot`HEf
z6BDo(7qJ~3vKt%6ZEeSHZqqa`(|&%`r>E7hu-4Yr*R{3Rwzk*2yw|<G*VotBy}j9x
zklEPS+LM#o*4EoRHrqWn+m@Ew*4EsZnBSwL;Av&xq@>}er{!*H?d|RE?(XmJ@9|Pe
z@l#6iRZa2L)$&_X^xod}eRuVKcle{8_^zq=uB!R9vH9)o`n|dOzPkFrz5Mt0{o&yK
z=jQ(M@&5Su|N8m=|NkefO3nZP04a1*PE!B}2pAb5A}ud5Nl;l_gqN7AtiQp*$ji&x
z<L2q=>+SOM_51t${QmxXRol}500C!7L_t(I%XO1yLxV67h6AluQ2_@iwQ8v|?ok!@
z-YYn2>#nW&|35*<zyR_6!g23IayfDUL{c=<a$MK3Oicl4WsM?=R5Y>)Th&V_rjo9T
ze#S;A*_l5-mq!xibJ6!wKOoo|s*V1UP3zFvtN+N2dmiZIe60TG@1IwnMAb&U!p;(&
z)}V2vi81~RiCPdeuW8l@`p5cH$Duxa;$!@DOgp9kjcwZ?dHnuDpI=KyB(DLJ)l<bW
zoxxc`n84x{*F^tn4pPDb4w4X;flmnsxF}7W6Q2;S47l5!ft+w9Tiklyc0>%Oiwu0v
z+v3DX>X<hk(K-YAl8rjqq`=N11-cTgK7f!ChttF#SK>cwFHCNP3#`xu`i0+p@$j}v
zvupdO%QTv77cWeCo)rccc+eBSa%GrMK}}rR1Tof<y7n<*v_mpD0#Rs{jvgnbx?`g!
p3@;%j2HGU^&BlLdxVKXn|6j&?*~IE2*wO$1002ovPDHLkV1fhnr_}%e

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/notifications/info.png b/themes/triangles/client/src/img/notifications/info.png
new file mode 100644
index 0000000000000000000000000000000000000000..67928e88c95fca77407947ff12e43039dd0b3067
GIT binary patch
literal 732
zcmV<20wev2P)<h;3K|Lk000e1NJLTq001BW001Be0{{R3M5Kzw00003b3#c}2nYz<
z;ZNWI000SaNLh0L0089x0089ykL8;@0000PbVXQnQ*UN;cVTj60C#tHE@^ISb7Ns}
zWiD@WXPfRk8UO$Q_E1bzMgRZ*B(~cizuzFW)hW2vD7Dfiw$dfI(<QdkCAZThxYH%L
z(j~UjCAZThx6>uJ(<QglCAZTix6>!M(<r#qEV<Mzy45ba)iAr&GP~9`z1BLu*E_z~
zJipjKzt}{<*+;_KNW<Do!`oBF+*QZjS;*d7$=+Sb-d)PxW6R%T%;06r;Azd_Z_na%
z(c^m4<bl=ZgVyGX*yoJd=#ks$mE7u>-Rha$>YCo`p5N@L;_j;B?zHCcxaje_>G8bj
z^1kZw$L#db@AlE~_S5k8)$#Y&^7q~J_~G^W<o5dT`uy|z{q_9)`u+a={{H>`|Nj5~
z|Ns9nkp&R|000qmQchC<2M8D$ElGrzn8C=}<MQ+M)u=IB0003yNkl<ZILn1oS6jkR
z3{6YvqJseLjT2FEBjO$?PR{@TGd>`_N%@rX)a0Z|ZgN7D!E%EzO4BF|T#Lrx_z7o7
zd?!cSUYQljo-O%8iN{bX7`_+_qS82+VrJ&JnEfw#z64H(U-54aTjihr0J@_p!#+G!
zy3)t@`pEEzh@4#i^|Mv;7Key@{s%shl_<~B(S5fjCT5W<>1t;HU|V=x3WPOt0|0}0
z;R+};#==wo_zLLBoFPTVXzf+G2XtgWM5*d=5kONq(p*3b_?p!LkqWp59$A+NrCECi
zE?74ypUGPQc2z*22i4BEz^)$kGw`7J#8S(ueFE+LvgEqg05q!2ySiGp5X?S+^Vhc>
zR)P_dI{|=~6(ejrC)y2Qa66arabZmMn}^G$K_mOghco%~CLizL=l=r{;7UBdz#9|*
O0000<MNUMnLSTYudwt~q

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/notifications/success.png b/themes/triangles/client/src/img/notifications/success.png
new file mode 100644
index 0000000000000000000000000000000000000000..d3998392dec4edddb1b62c2c9b81f6bc8c3e4132
GIT binary patch
literal 931
zcmV;U16=%xP)<h;3K|Lk000e1NJLTq001BW001Be0{{R3M5Kzw00003b3#c}2nYz<
z;ZNWI000SaNLh0L007_s007_tqF?^X0000PbVXQnQ*UN;cVTj60C#tHE@^ISb7Ns}
zWiD@WXPfRk8UO$RZct2AMgRZ*B(`lpzkoT$aVN2M52a}}zkoBidMmScD7SbXvUfAP
zeJHbeF}ZpjtaKc#b27PnGrD^+x_mFVdmpWJ7^!m}t#uu(bTYbp8?1CPx_mOad@i?o
zGrD{ks&f;jaWcDn5v6c4x_mOad=sW|6Q*$yq;L?Va1o?%4We%jqi+wSZwjDp5~Xny
zrg0Ugau=y{8mn|As(vf5fH1jxF}Zv)u!b_ad^50yG_Z#>yM8sXh&8)@HnE5{yMH&b
zi8!)}IJ|&4vWh&ugFU{3KE8xOzlA}+hC{%IMZkzm!;DhJkyFKyRK=22#*$UWlUBZ)
zR>qT8#*|sVoM_0SZO)>6)2n{dtbo+6fz_^p&$W-)xRcttr{c%C=hM&c;Lz{k((vNc
z@Z;k4?d0|E<o537_wVZW^6U5W?D+KT`Sb1g^zHfc?fLZX`1S7j^zZuh@cQ-f`u6tx
z`S$$!`273){{8*_{r&#_|NsAug0YnV001m>QchC<2M7rb5f~XBAS*37Npg6EmzbTd
zzP`c1#K_v+<K*x1^Yiug`1<|*{{H@<nC93300D1FL_t(I%XO3YTf#69h65^~SZ(X7
zMHIn>#*M~}1NYv0PuyGb-`g~4NNM?g=#$?2rOjRL0O0aSsuqeyLz*gifKz@Yg191z
zKL>l|7_uoQdqwWkkwdyK%Y$L$qHvJ!cjbnIeZ#AxJkseoWH&ZpK4mk%m=iCbf1Qqz
zTu8Qxldn8tjL$}lm15K{;^3>#m}rMo%mX9`Uwbh*KI%1IB%rcG?JhFD&iHE|>={5R
z(9G!4E&e^A{EhKx2w`_Z10fQfzcWIaePgxxPWjU$B<idKQDQhYgPA?FW$`4WYNdul
z8vZ-^po{c4<1kKtF?E4OrAI9eo7f>|M90H0=dj*NGj+-Ny})4&FI4Kv;zI(5W!X~C
zUYq=9P~`C<;j-pFNvaznhqborUbw;E+cw@2us2MiAKF!!+j5C68*eOdG_B3iQsUC~
zf}^?Ab#S<&p*YXzQAc}KVcy|29BtJBm-*)7Ur{cM|38<V#k&v!+G79!002ovPDHLk
FV1l=5+6w>x

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/notifications/warning.png b/themes/triangles/client/src/img/notifications/warning.png
new file mode 100644
index 0000000000000000000000000000000000000000..ab8b54ffc0ea43e214c5b0a9ca13a8b590ded293
GIT binary patch
literal 580
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy%*9TgAsieWw;%dH0CG7CJR*yM
z^m`CyyfQK10+1nD;u=vBoS#-wo>-L1P+nfHmzkGcoSayYs+V7sKKq@G6j0H$0G|-o
z|NsBro}_YrzQU{d+OKD7zL>B3e6H?`c{<PM>%Ew#`y9xfr~3@Z1+ro2RJG>m8qL$S
znrCV?&(>?6YtTC1sCB+c>jDrpYh7&Cy4<F9wL|M_r}ni@?dx6IH@dZN^l0De)4tuW
zeP^Q1-AOw4rs&+Cs`GG~&co?Ck7npRo~iR>w(iq8y3c@UF36%l#cL8k7YddH`2{nu
zb4W<)dbUiTaqz^g$Dclby}H6Mnt_3l+0(@_#Nu>o@X4}620T-m3x$N|_$<_Zxi6UO
z#sB(s?ca`0x@1-??YV86;6(=US2wog3cr4k!4t14H$f}^j_b{hPT^vg?4v($bVq(W
zKm8B)w9gM~zwM7*D_4BvynO4A|3VhRk^5e2Y)<88Xg}kvq@@4Kp5cJsmq(o|Cbcpi
zSpGnCqPI~3>jRC7B9V~n<p-i9nXY!tU#&M|=du+)*luVzFLC=Q_hRb8g<&7FAGk?I
z2^X;2%wU-Nh_k{>vt(klsYhDhGT*w&)XQlP>IKiN(4Ay_`qsC<)~{1nmVR!PD}COZ
z(3kxnC7)%nN;&_XJ{5J>)4TcQI?JNIU2Zq7t-5^t_xGRmjOKwIbEe*~^8khwgQu&X
J%Q~loCIDF$0tNs8

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/padlock.png b/themes/triangles/client/src/img/padlock.png
new file mode 100644
index 0000000000000000000000000000000000000000..31abbaeefcbc507ac59450a76b222d0f4e2ef298
GIT binary patch
literal 3265
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

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/password_white.png b/themes/triangles/client/src/img/password_white.png
new file mode 100644
index 0000000000000000000000000000000000000000..0b93ef3fb7535313d9378066d0485f69e6e627df
GIT binary patch
literal 3858
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

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/pendrive.png b/themes/triangles/client/src/img/pendrive.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa49178c326631167ec642aaacc117c38b31f6bf
GIT binary patch
literal 6721
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
Hcm4kWRQYbi

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/sharingan.png b/themes/triangles/client/src/img/sharingan.png
new file mode 100644
index 0000000000000000000000000000000000000000..526787d3be6a78d936b6a21941291994d0eca38e
GIT binary patch
literal 9213
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
G0sjMgjHl56

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/stores/.directory b/themes/triangles/client/src/img/stores/.directory
new file mode 100644
index 00000000..7bdc8daf
--- /dev/null
+++ b/themes/triangles/client/src/img/stores/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,57,25
+Version=3
+ViewMode=1
diff --git a/themes/triangles/client/src/img/stores/applestore-badge.svg b/themes/triangles/client/src/img/stores/applestore-badge.svg
new file mode 100644
index 00000000..ac111e59
--- /dev/null
+++ b/themes/triangles/client/src/img/stores/applestore-badge.svg
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="US_UK_Download_on_the" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+	 x="0px" y="0px" width="135px" height="40px" viewBox="0 0 135 40" enable-background="new 0 0 135 40" xml:space="preserve">
+<g>
+	<path fill="#A6A6A6" d="M130.197,40H4.729C2.122,40,0,37.872,0,35.267V4.726C0,2.12,2.122,0,4.729,0h125.468
+		C132.803,0,135,2.12,135,4.726v30.541C135,37.872,132.803,40,130.197,40L130.197,40z"/>
+	<path d="M134.032,35.268c0,2.116-1.714,3.83-3.834,3.83H4.729c-2.119,0-3.839-1.714-3.839-3.83V4.725
+		c0-2.115,1.72-3.835,3.839-3.835h125.468c2.121,0,3.834,1.72,3.834,3.835L134.032,35.268L134.032,35.268z"/>
+	<g>
+		<g>
+			<path fill="#FFFFFF" d="M30.128,19.784c-0.029-3.223,2.639-4.791,2.761-4.864c-1.511-2.203-3.853-2.504-4.676-2.528
+				c-1.967-0.207-3.875,1.177-4.877,1.177c-1.022,0-2.565-1.157-4.228-1.123c-2.14,0.033-4.142,1.272-5.24,3.196
+				c-2.266,3.923-0.576,9.688,1.595,12.859c1.086,1.553,2.355,3.287,4.016,3.226c1.625-0.067,2.232-1.036,4.193-1.036
+				c1.943,0,2.513,1.036,4.207,0.997c1.744-0.028,2.842-1.56,3.89-3.127c1.255-1.78,1.759-3.533,1.779-3.623
+				C33.507,24.924,30.161,23.647,30.128,19.784z"/>
+			<path fill="#FFFFFF" d="M26.928,10.306c0.874-1.093,1.472-2.58,1.306-4.089c-1.265,0.056-2.847,0.875-3.758,1.944
+				c-0.806,0.942-1.526,2.486-1.34,3.938C24.557,12.205,26.016,11.382,26.928,10.306z"/>
+		</g>
+	</g>
+	<g>
+		<path fill="#FFFFFF" d="M53.645,31.504h-2.271l-1.244-3.909h-4.324l-1.185,3.909h-2.211l4.284-13.308h2.646L53.645,31.504z
+			 M49.755,25.955L48.63,22.48c-0.119-0.355-0.342-1.191-0.671-2.507h-0.04c-0.131,0.566-0.342,1.402-0.632,2.507l-1.105,3.475
+			H49.755z"/>
+		<path fill="#FFFFFF" d="M64.662,26.588c0,1.632-0.441,2.922-1.323,3.869c-0.79,0.843-1.771,1.264-2.942,1.264
+			c-1.264,0-2.172-0.454-2.725-1.362h-0.04v5.055h-2.132V25.067c0-1.026-0.027-2.079-0.079-3.159h1.875l0.119,1.521h0.04
+			c0.711-1.146,1.79-1.718,3.238-1.718c1.132,0,2.077,0.447,2.833,1.342C64.284,23.949,64.662,25.127,64.662,26.588z M62.49,26.666
+			c0-0.934-0.21-1.704-0.632-2.31c-0.461-0.632-1.08-0.948-1.856-0.948c-0.526,0-1.004,0.176-1.431,0.523
+			c-0.428,0.35-0.708,0.807-0.839,1.373c-0.066,0.264-0.099,0.48-0.099,0.65v1.6c0,0.698,0.214,1.287,0.642,1.768
+			s0.984,0.721,1.668,0.721c0.803,0,1.428-0.31,1.875-0.928C62.266,28.496,62.49,27.68,62.49,26.666z"/>
+		<path fill="#FFFFFF" d="M75.699,26.588c0,1.632-0.441,2.922-1.324,3.869c-0.789,0.843-1.77,1.264-2.941,1.264
+			c-1.264,0-2.172-0.454-2.724-1.362H68.67v5.055h-2.132V25.067c0-1.026-0.027-2.079-0.079-3.159h1.875l0.119,1.521h0.04
+			c0.71-1.146,1.789-1.718,3.238-1.718c1.131,0,2.076,0.447,2.834,1.342C75.32,23.949,75.699,25.127,75.699,26.588z M73.527,26.666
+			c0-0.934-0.211-1.704-0.633-2.31c-0.461-0.632-1.078-0.948-1.855-0.948c-0.527,0-1.004,0.176-1.432,0.523
+			c-0.428,0.35-0.707,0.807-0.838,1.373c-0.065,0.264-0.099,0.48-0.099,0.65v1.6c0,0.698,0.214,1.287,0.64,1.768
+			c0.428,0.48,0.984,0.721,1.67,0.721c0.803,0,1.428-0.31,1.875-0.928C73.303,28.496,73.527,27.68,73.527,26.666z"/>
+		<path fill="#FFFFFF" d="M88.039,27.772c0,1.132-0.393,2.053-1.182,2.764c-0.867,0.777-2.074,1.165-3.625,1.165
+			c-1.432,0-2.58-0.276-3.449-0.829l0.494-1.777c0.936,0.566,1.963,0.85,3.082,0.85c0.803,0,1.428-0.182,1.877-0.544
+			c0.447-0.362,0.67-0.848,0.67-1.454c0-0.54-0.184-0.995-0.553-1.364c-0.367-0.369-0.98-0.712-1.836-1.029
+			c-2.33-0.869-3.494-2.142-3.494-3.816c0-1.094,0.408-1.991,1.225-2.689c0.814-0.699,1.9-1.048,3.258-1.048
+			c1.211,0,2.217,0.211,3.02,0.632l-0.533,1.738c-0.75-0.408-1.598-0.612-2.547-0.612c-0.75,0-1.336,0.185-1.756,0.553
+			c-0.355,0.329-0.533,0.73-0.533,1.205c0,0.526,0.203,0.961,0.611,1.303c0.355,0.316,1,0.658,1.936,1.027
+			c1.145,0.461,1.986,1,2.527,1.618C87.77,26.081,88.039,26.852,88.039,27.772z"/>
+		<path fill="#FFFFFF" d="M95.088,23.508h-2.35v4.659c0,1.185,0.414,1.777,1.244,1.777c0.381,0,0.697-0.033,0.947-0.099l0.059,1.619
+			c-0.42,0.157-0.973,0.236-1.658,0.236c-0.842,0-1.5-0.257-1.975-0.77c-0.473-0.514-0.711-1.376-0.711-2.587v-4.837h-1.4v-1.6h1.4
+			v-1.757l2.094-0.632v2.389h2.35V23.508z"/>
+		<path fill="#FFFFFF" d="M105.691,26.627c0,1.475-0.422,2.686-1.264,3.633c-0.883,0.975-2.055,1.461-3.516,1.461
+			c-1.408,0-2.529-0.467-3.365-1.401s-1.254-2.113-1.254-3.534c0-1.487,0.43-2.705,1.293-3.652c0.861-0.948,2.023-1.422,3.484-1.422
+			c1.408,0,2.541,0.467,3.396,1.402C105.283,24.021,105.691,25.192,105.691,26.627z M103.479,26.696
+			c0-0.885-0.189-1.644-0.572-2.277c-0.447-0.766-1.086-1.148-1.914-1.148c-0.857,0-1.508,0.383-1.955,1.148
+			c-0.383,0.634-0.572,1.405-0.572,2.317c0,0.885,0.189,1.644,0.572,2.276c0.461,0.766,1.105,1.148,1.936,1.148
+			c0.814,0,1.453-0.39,1.914-1.168C103.281,28.347,103.479,27.58,103.479,26.696z"/>
+		<path fill="#FFFFFF" d="M112.621,23.783c-0.211-0.039-0.436-0.059-0.672-0.059c-0.75,0-1.33,0.283-1.738,0.85
+			c-0.355,0.5-0.533,1.132-0.533,1.895v5.035h-2.131l0.02-6.574c0-1.106-0.027-2.113-0.08-3.021h1.857l0.078,1.836h0.059
+			c0.225-0.631,0.58-1.139,1.066-1.52c0.475-0.343,0.988-0.514,1.541-0.514c0.197,0,0.375,0.014,0.533,0.039V23.783z"/>
+		<path fill="#FFFFFF" d="M122.156,26.252c0,0.382-0.025,0.704-0.078,0.967h-6.396c0.025,0.948,0.334,1.673,0.928,2.173
+			c0.539,0.447,1.236,0.671,2.092,0.671c0.947,0,1.811-0.151,2.588-0.454l0.334,1.48c-0.908,0.396-1.98,0.593-3.217,0.593
+			c-1.488,0-2.656-0.438-3.506-1.313c-0.848-0.875-1.273-2.05-1.273-3.524c0-1.447,0.395-2.652,1.186-3.613
+			c0.828-1.026,1.947-1.539,3.355-1.539c1.383,0,2.43,0.513,3.141,1.539C121.873,24.047,122.156,25.055,122.156,26.252z
+			 M120.123,25.699c0.014-0.632-0.125-1.178-0.414-1.639c-0.369-0.593-0.936-0.889-1.699-0.889c-0.697,0-1.264,0.289-1.697,0.869
+			c-0.355,0.461-0.566,1.014-0.631,1.658H120.123z"/>
+	</g>
+	<g>
+		<g>
+			<path fill="#FFFFFF" d="M49.05,10.009c0,1.177-0.353,2.063-1.058,2.658c-0.653,0.549-1.581,0.824-2.783,0.824
+				c-0.596,0-1.106-0.026-1.533-0.078V6.982c0.557-0.09,1.157-0.136,1.805-0.136c1.145,0,2.008,0.249,2.59,0.747
+				C48.723,8.156,49.05,8.961,49.05,10.009z M47.945,10.038c0-0.763-0.202-1.348-0.606-1.756c-0.404-0.407-0.994-0.611-1.771-0.611
+				c-0.33,0-0.611,0.022-0.844,0.068v4.889c0.129,0.02,0.365,0.029,0.708,0.029c0.802,0,1.421-0.223,1.857-0.669
+				S47.945,10.892,47.945,10.038z"/>
+			<path fill="#FFFFFF" d="M54.909,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.009,0.718-1.727,0.718
+				c-0.692,0-1.243-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.712-0.698
+				c0.692,0,1.248,0.229,1.669,0.688C54.708,9.757,54.909,10.333,54.909,11.037z M53.822,11.071c0-0.435-0.094-0.808-0.281-1.119
+				c-0.22-0.376-0.533-0.564-0.94-0.564c-0.421,0-0.741,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
+				c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.714-0.191,0.94-0.574
+				C53.725,11.882,53.822,11.506,53.822,11.071z"/>
+			<path fill="#FFFFFF" d="M62.765,8.719l-1.475,4.714h-0.96l-0.611-2.047c-0.155-0.511-0.281-1.019-0.379-1.523h-0.019
+				c-0.091,0.518-0.217,1.025-0.379,1.523l-0.649,2.047h-0.971l-1.387-4.714h1.077l0.533,2.241c0.129,0.53,0.235,1.035,0.32,1.513
+				h0.019c0.078-0.394,0.207-0.896,0.389-1.503l0.669-2.25h0.854l0.641,2.202c0.155,0.537,0.281,1.054,0.378,1.552h0.029
+				c0.071-0.485,0.178-1.002,0.32-1.552l0.572-2.202H62.765z"/>
+			<path fill="#FFFFFF" d="M68.198,13.433H67.15v-2.7c0-0.832-0.316-1.248-0.95-1.248c-0.311,0-0.562,0.114-0.757,0.343
+				c-0.193,0.229-0.291,0.499-0.291,0.808v2.796h-1.048v-3.366c0-0.414-0.013-0.863-0.038-1.349h0.921l0.049,0.737h0.029
+				c0.122-0.229,0.304-0.418,0.543-0.569c0.284-0.176,0.602-0.265,0.95-0.265c0.44,0,0.806,0.142,1.097,0.427
+				c0.362,0.349,0.543,0.87,0.543,1.562V13.433z"/>
+			<path fill="#FFFFFF" d="M71.088,13.433h-1.047V6.556h1.047V13.433z"/>
+			<path fill="#FFFFFF" d="M77.258,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.01,0.718-1.727,0.718
+				c-0.693,0-1.244-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.711-0.698
+				c0.693,0,1.248,0.229,1.67,0.688C77.057,9.757,77.258,10.333,77.258,11.037z M76.17,11.071c0-0.435-0.094-0.808-0.281-1.119
+				c-0.219-0.376-0.533-0.564-0.939-0.564c-0.422,0-0.742,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
+				c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.713-0.191,0.939-0.574
+				C76.074,11.882,76.17,11.506,76.17,11.071z"/>
+			<path fill="#FFFFFF" d="M82.33,13.433h-0.941l-0.078-0.543h-0.029c-0.322,0.433-0.781,0.65-1.377,0.65
+				c-0.445,0-0.805-0.143-1.076-0.427c-0.246-0.258-0.369-0.579-0.369-0.96c0-0.576,0.24-1.015,0.723-1.319
+				c0.482-0.304,1.16-0.453,2.033-0.446V10.3c0-0.621-0.326-0.931-0.979-0.931c-0.465,0-0.875,0.117-1.229,0.349l-0.213-0.688
+				c0.438-0.271,0.979-0.407,1.617-0.407c1.232,0,1.85,0.65,1.85,1.95v1.736C82.262,12.78,82.285,13.155,82.33,13.433z
+				 M81.242,11.813v-0.727c-1.156-0.02-1.734,0.297-1.734,0.95c0,0.246,0.066,0.43,0.201,0.553c0.135,0.123,0.307,0.184,0.512,0.184
+				c0.23,0,0.445-0.073,0.641-0.218c0.197-0.146,0.318-0.331,0.363-0.558C81.236,11.946,81.242,11.884,81.242,11.813z"/>
+			<path fill="#FFFFFF" d="M88.285,13.433h-0.93l-0.049-0.757h-0.029c-0.297,0.576-0.803,0.864-1.514,0.864
+				c-0.568,0-1.041-0.223-1.416-0.669s-0.562-1.025-0.562-1.736c0-0.763,0.203-1.381,0.611-1.853c0.395-0.44,0.879-0.66,1.455-0.66
+				c0.633,0,1.076,0.213,1.328,0.64h0.02V6.556h1.049v5.607C88.248,12.622,88.26,13.045,88.285,13.433z M87.199,11.445v-0.786
+				c0-0.136-0.01-0.246-0.029-0.33c-0.059-0.252-0.186-0.464-0.379-0.635c-0.195-0.171-0.43-0.257-0.701-0.257
+				c-0.391,0-0.697,0.155-0.922,0.466c-0.223,0.311-0.336,0.708-0.336,1.193c0,0.466,0.107,0.844,0.322,1.135
+				c0.227,0.31,0.533,0.465,0.916,0.465c0.344,0,0.619-0.129,0.828-0.388C87.1,12.069,87.199,11.781,87.199,11.445z"/>
+			<path fill="#FFFFFF" d="M97.248,11.037c0,0.725-0.207,1.319-0.621,1.785c-0.434,0.479-1.008,0.718-1.727,0.718
+				c-0.691,0-1.242-0.229-1.654-0.689c-0.41-0.459-0.615-1.038-0.615-1.736c0-0.73,0.211-1.329,0.635-1.794s0.994-0.698,1.713-0.698
+				c0.691,0,1.248,0.229,1.668,0.688C97.047,9.757,97.248,10.333,97.248,11.037z M96.162,11.071c0-0.435-0.094-0.808-0.281-1.119
+				c-0.221-0.376-0.533-0.564-0.941-0.564c-0.42,0-0.74,0.188-0.961,0.564c-0.188,0.311-0.281,0.69-0.281,1.138
+				c0,0.435,0.094,0.808,0.281,1.119c0.227,0.376,0.543,0.564,0.951,0.564c0.4,0,0.715-0.191,0.941-0.574
+				C96.064,11.882,96.162,11.506,96.162,11.071z"/>
+			<path fill="#FFFFFF" d="M102.883,13.433h-1.047v-2.7c0-0.832-0.316-1.248-0.951-1.248c-0.311,0-0.562,0.114-0.756,0.343
+				s-0.291,0.499-0.291,0.808v2.796h-1.049v-3.366c0-0.414-0.012-0.863-0.037-1.349h0.92l0.049,0.737h0.029
+				c0.123-0.229,0.305-0.418,0.543-0.569c0.285-0.176,0.602-0.265,0.951-0.265c0.439,0,0.805,0.142,1.096,0.427
+				c0.363,0.349,0.543,0.87,0.543,1.562V13.433z"/>
+			<path fill="#FFFFFF" d="M109.936,9.504h-1.154v2.29c0,0.582,0.205,0.873,0.611,0.873c0.188,0,0.344-0.016,0.467-0.049
+				l0.027,0.795c-0.207,0.078-0.479,0.117-0.814,0.117c-0.414,0-0.736-0.126-0.969-0.378c-0.234-0.252-0.35-0.676-0.35-1.271V9.504
+				h-0.689V8.719h0.689V7.855l1.027-0.31v1.173h1.154V9.504z"/>
+			<path fill="#FFFFFF" d="M115.484,13.433h-1.049v-2.68c0-0.845-0.316-1.268-0.949-1.268c-0.486,0-0.818,0.245-1,0.735
+				c-0.031,0.103-0.049,0.229-0.049,0.377v2.835h-1.047V6.556h1.047v2.841h0.02c0.33-0.517,0.803-0.775,1.416-0.775
+				c0.434,0,0.793,0.142,1.078,0.427c0.355,0.355,0.533,0.883,0.533,1.581V13.433z"/>
+			<path fill="#FFFFFF" d="M121.207,10.853c0,0.188-0.014,0.346-0.039,0.475h-3.143c0.014,0.466,0.164,0.821,0.455,1.067
+				c0.266,0.22,0.609,0.33,1.029,0.33c0.465,0,0.889-0.074,1.271-0.223l0.164,0.728c-0.447,0.194-0.973,0.291-1.582,0.291
+				c-0.73,0-1.305-0.215-1.721-0.645c-0.418-0.43-0.625-1.007-0.625-1.731c0-0.711,0.193-1.303,0.582-1.775
+				c0.406-0.504,0.955-0.756,1.648-0.756c0.678,0,1.193,0.252,1.541,0.756C121.068,9.77,121.207,10.265,121.207,10.853z
+				 M120.207,10.582c0.008-0.311-0.061-0.579-0.203-0.805c-0.182-0.291-0.459-0.437-0.834-0.437c-0.342,0-0.621,0.142-0.834,0.427
+				c-0.174,0.227-0.277,0.498-0.311,0.815H120.207z"/>
+		</g>
+	</g>
+</g>
+</svg>
diff --git a/themes/triangles/client/src/img/stores/googleplay-badge.svg b/themes/triangles/client/src/img/stores/googleplay-badge.svg
new file mode 100644
index 00000000..9e33e3aa
--- /dev/null
+++ b/themes/triangles/client/src/img/stores/googleplay-badge.svg
@@ -0,0 +1,429 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   xml:space="preserve"
+   width="135.71649"
+   height="40.018951"
+   viewBox="0 0 135.71649 40.018951"
+   sodipodi:docname="google-play-badge.svg"><metadata
+     id="metadata8"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+     id="defs6"><linearGradient
+       x1="31.7997"
+       y1="183.2903"
+       x2="15.0173"
+       y2="166.5079"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
+       spreadMethod="pad"
+       id="linearGradient50"><stop
+         style="stop-opacity:1;stop-color:#00a0ff"
+         offset="0"
+         id="stop52" /><stop
+         style="stop-opacity:1;stop-color:#00a1ff"
+         offset="0.0066"
+         id="stop54" /><stop
+         style="stop-opacity:1;stop-color:#00beff"
+         offset="0.2601"
+         id="stop56" /><stop
+         style="stop-opacity:1;stop-color:#00d2ff"
+         offset="0.5122"
+         id="stop58" /><stop
+         style="stop-opacity:1;stop-color:#00dfff"
+         offset="0.7604"
+         id="stop60" /><stop
+         style="stop-opacity:1;stop-color:#00e3ff"
+         offset="1"
+         id="stop62" /></linearGradient><linearGradient
+       x1="43.8344"
+       y1="171.9986"
+       x2="19.637501"
+       y2="171.9986"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
+       spreadMethod="pad"
+       id="linearGradient68"><stop
+         style="stop-opacity:1;stop-color:#ffe000"
+         offset="0"
+         id="stop70" /><stop
+         style="stop-opacity:1;stop-color:#ffbd00"
+         offset="0.4087"
+         id="stop72" /><stop
+         style="stop-opacity:1;stop-color:#ffa500"
+         offset="0.7754"
+         id="stop74" /><stop
+         style="stop-opacity:1;stop-color:#ff9c00"
+         offset="1"
+         id="stop76" /></linearGradient><linearGradient
+       x1="34.827"
+       y1="169.7039"
+       x2="12.0687"
+       y2="146.9456"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
+       spreadMethod="pad"
+       id="linearGradient82"><stop
+         style="stop-opacity:1;stop-color:#ff3a44"
+         offset="0"
+         id="stop84" /><stop
+         style="stop-opacity:1;stop-color:#c31162"
+         offset="1"
+         id="stop86" /></linearGradient><linearGradient
+       x1="17.2973"
+       y1="191.82381"
+       x2="27.4599"
+       y2="181.6613"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8,0,0,-0.8,0,161.6)"
+       spreadMethod="pad"
+       id="linearGradient92"><stop
+         style="stop-opacity:1;stop-color:#32a071"
+         offset="0"
+         id="stop94" /><stop
+         style="stop-opacity:1;stop-color:#2da771"
+         offset="0.0685"
+         id="stop96" /><stop
+         style="stop-opacity:1;stop-color:#15cf74"
+         offset="0.4762"
+         id="stop98" /><stop
+         style="stop-opacity:1;stop-color:#06e775"
+         offset="0.8009"
+         id="stop100" /><stop
+         style="stop-opacity:1;stop-color:#00f076"
+         offset="1"
+         id="stop102" /></linearGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath110"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path112"
+         inkscape:connector-curvature="0" /></clipPath><mask
+       maskUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       id="mask114"><g
+         id="g116"><g
+           clip-path="url(#clipPath110)"
+           id="g118"><path
+             d="M 0,0 124,0 124,48 0,48 0,0 Z"
+             style="fill:#000000;fill-opacity:0.2;fill-rule:nonzero;stroke:none"
+             id="path120"
+             inkscape:connector-curvature="0" /></g></g></mask><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath126"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path128"
+         inkscape:connector-curvature="0" /></clipPath><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath130"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path132"
+         inkscape:connector-curvature="0" /></clipPath><pattern
+       patternTransform="matrix(1,0,0,-1,0,48)"
+       patternUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="124"
+       height="48"
+       id="pattern134"><g
+         id="g136" /><g
+         id="g138"><g
+           clip-path="url(#clipPath130)"
+           id="g140"><g
+             id="g142"><path
+               d="M 29.625,20.695 18.012,14.098 C 17.363,13.727 16.781,13.754 16.406,14.09 l -0.058,-0.063 0.058,-0.058 c 0.375,-0.336 0.957,-0.36 1.606,0.011 l 11.687,6.641 -0.074,0.074 z"
+               style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+               id="path144" /></g></g></g></pattern><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath158"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path160"
+         inkscape:connector-curvature="0" /></clipPath><mask
+       maskUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       id="mask162"><g
+         id="g164"><g
+           clip-path="url(#clipPath158)"
+           id="g166"><path
+             d="M 0,0 124,0 124,48 0,48 0,0 Z"
+             style="fill:#000000;fill-opacity:0.12000002;fill-rule:nonzero;stroke:none"
+             id="path168"
+             inkscape:connector-curvature="0" /></g></g></mask><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath174"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path176"
+         inkscape:connector-curvature="0" /></clipPath><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath178"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path180"
+         inkscape:connector-curvature="0" /></clipPath><pattern
+       patternTransform="matrix(1,0,0,-1,0,48)"
+       patternUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="124"
+       height="48"
+       id="pattern182"><g
+         id="g184" /><g
+         id="g186"><g
+           clip-path="url(#clipPath178)"
+           id="g188"><g
+             id="g190"><path
+               d="m 16.348,14.145 c -0.235,0.246 -0.371,0.628 -0.371,1.125 l 0,-0.118 c 0,-0.496 0.136,-0.879 0.371,-1.125 l 0.058,0.063 -0.058,0.055 z"
+               style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+               id="path192" /></g></g></g></pattern><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath206"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path208"
+         inkscape:connector-curvature="0" /></clipPath><mask
+       maskUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       id="mask210"><g
+         id="g212"><g
+           clip-path="url(#clipPath206)"
+           id="g214"><path
+             d="M 0,0 124,0 124,48 0,48 0,0 Z"
+             style="fill:#000000;fill-opacity:0.12000002;fill-rule:nonzero;stroke:none"
+             id="path216"
+             inkscape:connector-curvature="0" /></g></g></mask><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath222"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path224"
+         inkscape:connector-curvature="0" /></clipPath><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath226"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path228"
+         inkscape:connector-curvature="0" /></clipPath><pattern
+       patternTransform="matrix(1,0,0,-1,0,48)"
+       patternUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="124"
+       height="48"
+       id="pattern230"><g
+         id="g232" /><g
+         id="g234"><g
+           clip-path="url(#clipPath226)"
+           id="g236"><g
+             id="g238"><path
+               d="m 33.613,22.961 -3.988,-2.266 0.074,-0.074 3.914,2.223 c 0.559,0.316 0.836,0.734 0.836,1.156 -0.047,-0.379 -0.332,-0.75 -0.836,-1.039 z"
+               style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+               id="path240" /></g></g></g></pattern><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath254"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path256"
+         inkscape:connector-curvature="0" /></clipPath><mask
+       maskUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       id="mask258"><g
+         id="g260"><g
+           clip-path="url(#clipPath254)"
+           id="g262"><path
+             d="M 0,0 124,0 124,48 0,48 0,0 Z"
+             style="fill:#000000;fill-opacity:0.25;fill-rule:nonzero;stroke:none"
+             id="path264"
+             inkscape:connector-curvature="0" /></g></g></mask><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath270"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path272"
+         inkscape:connector-curvature="0" /></clipPath><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath274"><path
+         d="M 0,0 124,0 124,48 0,48 0,0 Z"
+         id="path276"
+         inkscape:connector-curvature="0" /></clipPath><pattern
+       patternTransform="matrix(1,0,0,-1,0,48)"
+       patternUnits="userSpaceOnUse"
+       x="0"
+       y="0"
+       width="124"
+       height="48"
+       id="pattern278"><g
+         id="g280" /><g
+         id="g282"><g
+           clip-path="url(#clipPath274)"
+           id="g284"><g
+             id="g286"><path
+               d="m 18.012,33.902 15.601,-8.863 c 0.508,-0.289 0.789,-0.66 0.836,-1.039 0,0.418 -0.277,0.836 -0.836,1.156 L 18.012,34.02 c -1.117,0.632 -2.035,0.105 -2.035,-1.176 l 0,-0.114 c 0,1.278 0.918,1.805 2.035,1.172 z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+               id="path288" /></g></g></g></pattern></defs><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1366"
+     inkscape:window-height="705"
+     id="namedview4"
+     showgrid="false"
+     inkscape:zoom="7.6276974"
+     inkscape:cx="93.965168"
+     inkscape:cy="29.61582"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g10" /><g
+     id="g10"
+     inkscape:groupmode="layer"
+     inkscape:label="google-play-badge"
+     transform="matrix(1.25,0,0,-1.25,-9.4247625,49.85025)"><g
+       id="g12"
+       transform="matrix(1.0023923,0,0,0.99072975,-0.29664807,0)"><path
+         d="M 112,8 12,8 C 9.801,8 8,9.801 8,12 l 0,24 c 0,2.199 1.801,4 4,4 l 100,0 c 2.199,0 4,-1.801 4,-4 l 0,-24 c 0,-2.199 -1.801,-4 -4,-4 z"
+         style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path14"
+         inkscape:connector-curvature="0" /><path
+         d="m 112,39.359 c 1.852,0 3.359,-1.507 3.359,-3.359 l 0,-24 c 0,-1.852 -1.507,-3.359 -3.359,-3.359 l -100,0 c -1.852,0 -3.359,1.507 -3.359,3.359 l 0,24 c 0,1.852 1.507,3.359 3.359,3.359 l 100,0 M 112,40 12,40 C 9.801,40 8,38.199 8,36 L 8,12 C 8,9.801 9.801,8 12,8 l 100,0 c 2.199,0 4,1.801 4,4 l 0,24 c 0,2.199 -1.801,4 -4,4 z"
+         style="fill:#a6a6a6;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path16"
+         inkscape:connector-curvature="0" /><g
+         id="g18"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 45.934,16.195 c 0,0.668 -0.2,1.203 -0.594,1.602 -0.453,0.473 -1.043,0.711 -1.766,0.711 -0.691,0 -1.281,-0.242 -1.765,-0.719 -0.485,-0.484 -0.727,-1.078 -0.727,-1.789 0,-0.711 0.242,-1.305 0.727,-1.785 0.484,-0.481 1.074,-0.723 1.765,-0.723 0.344,0 0.672,0.071 0.985,0.203 0.312,0.133 0.566,0.313 0.75,0.535 l -0.418,0.422 c -0.321,-0.379 -0.758,-0.566 -1.317,-0.566 -0.504,0 -0.941,0.176 -1.312,0.531 -0.367,0.356 -0.551,0.817 -0.551,1.383 0,0.566 0.184,1.031 0.551,1.387 0.371,0.351 0.808,0.531 1.312,0.531 0.535,0 0.985,-0.18 1.34,-0.535 0.234,-0.235 0.367,-0.559 0.402,-0.973 l -1.742,0 0,-0.578 2.324,0 c 0.028,0.125 0.036,0.246 0.036,0.363 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path20"
+           inkscape:connector-curvature="0" /></g><g
+         id="g22"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 49.621,14.191 -2.183,0 0,1.52 1.968,0 0,0.578 -1.968,0 0,1.52 2.183,0 0,0.589 -2.801,0 0,-4.796 2.801,0 0,0.589 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path24"
+           inkscape:connector-curvature="0" /></g><g
+         id="g26"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 52.223,18.398 -0.618,0 0,-4.207 -1.339,0 0,-0.589 3.297,0 0,0.589 -1.34,0 0,4.207 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path28"
+           inkscape:connector-curvature="0" /></g><g
+         id="g30"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 55.949,18.398 0,-4.796 0.617,0 0,4.796 -0.617,0 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path32"
+           inkscape:connector-curvature="0" /></g><g
+         id="g34"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 59.301,18.398 -0.613,0 0,-4.207 -1.344,0 0,-0.589 3.301,0 0,0.589 -1.344,0 0,4.207 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path36"
+           inkscape:connector-curvature="0" /></g><g
+         id="g38"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 66.887,17.781 c -0.473,0.485 -1.059,0.727 -1.758,0.727 -0.703,0 -1.289,-0.242 -1.762,-0.727 C 62.895,17.297 62.66,16.703 62.66,16 c 0,-0.703 0.235,-1.297 0.707,-1.781 0.473,-0.485 1.059,-0.727 1.762,-0.727 0.695,0 1.281,0.242 1.754,0.731 0.476,0.488 0.711,1.078 0.711,1.777 0,0.703 -0.235,1.297 -0.707,1.781 z m -3.063,-0.402 c 0.356,0.359 0.789,0.539 1.305,0.539 0.512,0 0.949,-0.18 1.301,-0.539 0.355,-0.359 0.535,-0.82 0.535,-1.379 0,-0.559 -0.18,-1.02 -0.535,-1.379 -0.352,-0.359 -0.789,-0.539 -1.301,-0.539 -0.516,0 -0.949,0.18 -1.305,0.539 -0.355,0.359 -0.535,0.82 -0.535,1.379 0,0.559 0.18,1.02 0.535,1.379 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path40"
+           inkscape:connector-curvature="0" /></g><g
+         id="g42"
+         transform="matrix(1,0,0,-1,0,48)"><path
+           d="m 68.461,18.398 0,-4.796 0.75,0 2.332,3.73 0.027,0 -0.027,-0.922 0,-2.808 0.617,0 0,4.796 -0.644,0 -2.442,-3.914 -0.027,0 0.027,0.926 0,2.988 -0.613,0 z"
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+           id="path44"
+           inkscape:connector-curvature="0" /></g><path
+         d="m 62.508,22.598 c -1.879,0 -3.414,-1.43 -3.414,-3.403 0,-1.957 1.535,-3.402 3.414,-3.402 1.883,0 3.418,1.445 3.418,3.402 0,1.973 -1.535,3.403 -3.418,3.403 z m 0,-5.465 c -1.031,0 -1.918,0.851 -1.918,2.062 0,1.227 0.887,2.063 1.918,2.063 1.031,0 1.922,-0.836 1.922,-2.063 0,-1.211 -0.891,-2.062 -1.922,-2.062 z m -7.449,5.465 c -1.883,0 -3.414,-1.43 -3.414,-3.403 0,-1.957 1.531,-3.402 3.414,-3.402 1.882,0 3.414,1.445 3.414,3.402 0,1.973 -1.532,3.403 -3.414,3.403 z m 0,-5.465 c -1.032,0 -1.922,0.851 -1.922,2.062 0,1.227 0.89,2.063 1.922,2.063 1.031,0 1.918,-0.836 1.918,-2.063 0,-1.211 -0.887,-2.062 -1.918,-2.062 z m -8.864,4.422 0,-1.446 3.453,0 c -0.101,-0.808 -0.371,-1.402 -0.785,-1.816 -0.504,-0.5 -1.289,-1.055 -2.668,-1.055 -2.125,0 -3.789,1.715 -3.789,3.84 0,2.125 1.664,3.84 3.789,3.84 1.149,0 1.985,-0.449 2.602,-1.031 l 1.019,1.019 c -0.863,0.824 -2.011,1.457 -3.621,1.457 -2.914,0 -5.363,-2.371 -5.363,-5.285 0,-2.914 2.449,-5.285 5.363,-5.285 1.575,0 2.758,0.516 3.688,1.484 0.953,0.953 1.25,2.293 1.25,3.375 0,0.336 -0.028,0.645 -0.078,0.903 l -4.86,0 z m 36.246,-1.121 c -0.281,0.761 -1.148,2.164 -2.914,2.164 -1.75,0 -3.207,-1.379 -3.207,-3.403 0,-1.906 1.442,-3.402 3.375,-3.402 1.563,0 2.465,0.953 2.836,1.508 l -1.16,0.773 c -0.387,-0.566 -0.914,-0.941 -1.676,-0.941 -0.757,0 -1.3,0.347 -1.648,1.031 l 4.551,1.883 -0.157,0.387 z m -4.64,-1.133 c -0.039,1.312 1.019,1.984 1.777,1.984 0.594,0 1.098,-0.297 1.266,-0.722 L 77.801,19.301 Z M 74.102,16 l 1.496,0 0,10 -1.496,0 0,-10 z m -2.45,5.84 -0.05,0 c -0.336,0.398 -0.977,0.758 -1.789,0.758 -1.704,0 -3.262,-1.496 -3.262,-3.414 0,-1.907 1.558,-3.391 3.262,-3.391 0.812,0 1.453,0.363 1.789,0.773 l 0.05,0 0,-0.488 c 0,-1.301 -0.695,-2 -1.816,-2 -0.914,0 -1.481,0.66 -1.715,1.215 L 66.82,14.75 c 0.375,-0.902 1.368,-2.012 3.016,-2.012 1.754,0 3.234,1.032 3.234,3.543 l 0,6.11 -1.418,0 0,-0.551 z m -1.711,-4.707 c -1.031,0 -1.894,0.863 -1.894,2.051 0,1.199 0.863,2.074 1.894,2.074 1.016,0 1.817,-0.875 1.817,-2.074 0,-1.188 -0.801,-2.051 -1.817,-2.051 z M 89.445,26 l -3.578,0 0,-10 1.492,0 0,3.789 2.086,0 c 1.657,0 3.282,1.199 3.282,3.106 0,1.906 -1.629,3.105 -3.282,3.105 z m 0.039,-4.82 -2.125,0 0,3.429 2.125,0 c 1.114,0 1.75,-0.925 1.75,-1.714 0,-0.774 -0.636,-1.715 -1.75,-1.715 z m 9.223,1.437 c -1.078,0 -2.199,-0.476 -2.66,-1.531 l 1.324,-0.555 c 0.285,0.555 0.809,0.735 1.363,0.735 0.774,0 1.559,-0.465 1.571,-1.286 l 0,-0.105 c -0.27,0.156 -0.848,0.387 -1.559,0.387 -1.426,0 -2.879,-0.785 -2.879,-2.25 0,-1.34 1.168,-2.203 2.481,-2.203 1.004,0 1.558,0.453 1.906,0.98 l 0.051,0 0,-0.773 1.441,0 0,3.836 c 0,1.773 -1.324,2.765 -3.039,2.765 z m -0.18,-5.48 c -0.488,0 -1.168,0.242 -1.168,0.847 0,0.774 0.848,1.071 1.582,1.071 0.657,0 0.965,-0.145 1.364,-0.336 -0.117,-0.926 -0.914,-1.582 -1.778,-1.582 z m 8.469,5.261 -1.715,-4.335 -0.051,0 -1.773,4.335 -1.609,0 2.664,-6.058 -1.52,-3.371 1.559,0 4.105,9.429 -1.66,0 z M 93.547,16 l 1.496,0 0,10 -1.496,0 0,-10 z"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         id="path46"
+         inkscape:connector-curvature="0" /><g
+         id="g48"><path
+           d="M 16.348,33.969 C 16.113,33.723 15.977,33.34 15.977,32.844 l 0,-17.692 c 0,-0.496 0.136,-0.879 0.371,-1.125 l 0.058,-0.054 9.914,9.91 0,0.234 -9.914,9.91 -0.058,-0.058 z"
+           style="fill:url(#linearGradient50);fill-opacity:1;fill-rule:nonzero;stroke:none"
+           id="path64"
+           inkscape:connector-curvature="0" /></g><g
+         id="g66"><path
+           d="m 29.621,20.578 -3.301,3.305 0,0.234 3.305,3.305 0.074,-0.043 3.914,-2.227 c 1.117,-0.632 1.117,-1.672 0,-2.308 l -3.914,-2.223 -0.078,-0.043 z"
+           style="fill:url(#linearGradient68);fill-opacity:1;fill-rule:nonzero;stroke:none"
+           id="path78"
+           inkscape:connector-curvature="0" /></g><g
+         id="g80"><path
+           d="M 29.699,20.621 26.32,24 16.348,14.027 c 0.371,-0.39 0.976,-0.437 1.664,-0.047 l 11.687,6.641"
+           style="fill:url(#linearGradient82);fill-opacity:1;fill-rule:nonzero;stroke:none"
+           id="path88"
+           inkscape:connector-curvature="0" /></g><g
+         id="g90"><path
+           d="M 29.699,27.379 18.012,34.02 c -0.688,0.386 -1.293,0.339 -1.664,-0.051 L 26.32,24 l 3.379,3.379 z"
+           style="fill:url(#linearGradient92);fill-opacity:1;fill-rule:nonzero;stroke:none"
+           id="path104"
+           inkscape:connector-curvature="0" /></g><g
+         id="g106"><g
+           id="g108" /><g
+           id="g122"
+           mask="url(#mask114)"><g
+             id="g124" /><g
+             id="g146"><g
+               clip-path="url(#clipPath126)"
+               id="g148"><g
+                 id="g150"><path
+                   d="M 0,0 124,0 124,48 0,48 0,0 Z"
+                   style="fill:url(#pattern134);fill-opacity:1;fill-rule:nonzero;stroke:none"
+                   id="path152"
+                   inkscape:connector-curvature="0" /></g></g></g></g></g><g
+         id="g154"><g
+           id="g156" /><g
+           id="g170"
+           mask="url(#mask162)"><g
+             id="g172" /><g
+             id="g194"><g
+               clip-path="url(#clipPath174)"
+               id="g196"><g
+                 id="g198"><path
+                   d="M 0,0 124,0 124,48 0,48 0,0 Z"
+                   style="fill:url(#pattern182);fill-opacity:1;fill-rule:nonzero;stroke:none"
+                   id="path200"
+                   inkscape:connector-curvature="0" /></g></g></g></g></g><g
+         id="g202"><g
+           id="g204" /><g
+           id="g218"
+           mask="url(#mask210)"><g
+             id="g220" /><g
+             id="g242"><g
+               clip-path="url(#clipPath222)"
+               id="g244"><g
+                 id="g246"><path
+                   d="M 0,0 124,0 124,48 0,48 0,0 Z"
+                   style="fill:url(#pattern230);fill-opacity:1;fill-rule:nonzero;stroke:none"
+                   id="path248"
+                   inkscape:connector-curvature="0" /></g></g></g></g></g><g
+         id="g250"><g
+           id="g252" /><g
+           id="g266"
+           mask="url(#mask258)"><g
+             id="g268" /><g
+             id="g290"><g
+               clip-path="url(#clipPath270)"
+               id="g292"><g
+                 id="g294"><path
+                   d="M 0,0 124,0 124,48 0,48 0,0 Z"
+                   style="fill:url(#pattern278);fill-opacity:1;fill-rule:nonzero;stroke:none"
+                   id="path296"
+                   inkscape:connector-curvature="0" /></g></g></g></g></g></g></g></svg>
\ No newline at end of file
diff --git a/themes/triangles/client/src/img/success.png b/themes/triangles/client/src/img/success.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee9d6841bbb7208220a831c1c42917eb8facffa5
GIT binary patch
literal 3147
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

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/user.png b/themes/triangles/client/src/img/user.png
new file mode 100644
index 0000000000000000000000000000000000000000..00941399d34f0f2d85d323f61daa67c2c00c494d
GIT binary patch
literal 2933
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

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/img/warning.png b/themes/triangles/client/src/img/warning.png
new file mode 100644
index 0000000000000000000000000000000000000000..c6acd953b142a8e43e293c353a60e6313a8d39eb
GIT binary patch
literal 4038
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
F{{xrIJYfI;

literal 0
HcmV?d00001

diff --git a/themes/triangles/client/src/index.ts b/themes/triangles/client/src/index.ts
new file mode 100644
index 00000000..802004a8
--- /dev/null
+++ b/themes/triangles/client/src/index.ts
@@ -0,0 +1,34 @@
+
+import FirstFactorValidator = require("./lib/firstfactor/FirstFactorValidator");
+
+import FirstFactor from "./lib/firstfactor/index";
+import SecondFactor from "./lib/secondfactor/index";
+import TOTPRegister from "./lib/totp-register/totp-register";
+import U2fRegister from "./lib/u2f-register/u2f-register";
+import ResetPasswordRequest from "./lib/reset-password/reset-password-request";
+import ResetPasswordForm from "./lib/reset-password/reset-password-form";
+import jslogger = require("js-logger");
+import jQuery = require("jquery");
+import Endpoints = require("../../shared/api");
+
+jslogger.useDefaults();
+jslogger.setLevel(jslogger.INFO);
+
+(function () {
+  (<any>window).jQuery = jQuery;
+  require("bootstrap");
+
+  jQuery('[data-toggle="tooltip"]').tooltip();
+  if (window.location.pathname == Endpoints.FIRST_FACTOR_GET)
+    FirstFactor(window, jQuery, FirstFactorValidator, jslogger);
+  else if (window.location.pathname == Endpoints.SECOND_FACTOR_GET)
+    SecondFactor(window, jQuery, (global as any).u2f);
+  else if (window.location.pathname == Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET)
+    TOTPRegister(window, jQuery);
+  else if (window.location.pathname == Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET)
+    U2fRegister(window, jQuery, (global as any).u2f);
+  else if (window.location.pathname == Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET)
+    ResetPasswordForm(window, jQuery);
+  else if (window.location.pathname == Endpoints.RESET_PASSWORD_REQUEST_GET)
+    ResetPasswordRequest(window, jQuery);
+})();
diff --git a/themes/triangles/client/src/lib/GetPromised.ts b/themes/triangles/client/src/lib/GetPromised.ts
new file mode 100644
index 00000000..77913965
--- /dev/null
+++ b/themes/triangles/client/src/lib/GetPromised.ts
@@ -0,0 +1,14 @@
+import BluebirdPromise = require("bluebird");
+
+export default function ($: JQueryStatic, url: string, data: Object, fn: any,
+  dataType: string): BluebirdPromise<any> {
+  return new BluebirdPromise<any>((resolve, reject) => {
+    $.get(url, {}, undefined, dataType)
+      .done((data: any) => {
+        resolve(data);
+      })
+      .fail((xhr: JQueryXHR, textStatus: string) => {
+        reject(textStatus);
+      });
+  });
+}
\ No newline at end of file
diff --git a/themes/triangles/client/src/lib/INotifier.ts b/themes/triangles/client/src/lib/INotifier.ts
new file mode 100644
index 00000000..df947538
--- /dev/null
+++ b/themes/triangles/client/src/lib/INotifier.ts
@@ -0,0 +1,14 @@
+
+declare type Handler = () => void;
+
+export interface Handlers {
+  onFadedIn: Handler;
+  onFadedOut: Handler;
+}
+
+export interface INotifier {
+  success(msg: string, handlers?: Handlers): void;
+  error(msg: string, handlers?: Handlers): void;
+  warning(msg: string, handlers?: Handlers): void;
+  info(msg: string, handlers?: Handlers): void;
+}
\ No newline at end of file
diff --git a/themes/triangles/client/src/lib/Notifier.ts b/themes/triangles/client/src/lib/Notifier.ts
new file mode 100644
index 00000000..c0252b9b
--- /dev/null
+++ b/themes/triangles/client/src/lib/Notifier.ts
@@ -0,0 +1,83 @@
+
+
+import util = require("util");
+import { INotifier, Handlers } from "./INotifier";
+
+class NotificationEvent {
+  private element: JQuery;
+  private message: string;
+  private statusType: string;
+  private timeoutId: any;
+
+  constructor(element: JQuery, msg: string, statusType: string) {
+    this.message = msg;
+    this.statusType = statusType;
+    this.element = element;
+  }
+
+  private clearNotification() {
+    this.element.removeClass(this.statusType);
+    this.element.html("");
+  }
+
+  start(handlers?: Handlers) {
+    const that = this;
+    const FADE_TIME = 500;
+    const html = util.format('<i><img src="/img/notifications/%s.png" alt="status %s"/></i>\
+      <span>%s</span>', this.statusType, this.statusType, this.message);
+    this.element.html(html);
+    this.element.addClass(this.statusType);
+    this.element.fadeIn(FADE_TIME, function () {
+      if (handlers)
+        handlers.onFadedIn();
+    });
+
+    this.timeoutId = setTimeout(function () {
+      that.element.fadeOut(FADE_TIME, function () {
+        that.clearNotification();
+        if (handlers)
+          handlers.onFadedOut();
+      });
+    }, 4000);
+  }
+
+  interrupt() {
+    this.clearNotification();
+    this.element.hide();
+    clearTimeout(this.timeoutId);
+  }
+}
+
+export class Notifier implements INotifier {
+  private element: JQuery;
+  private onGoingEvent: NotificationEvent;
+
+  constructor(selector: string, $: JQueryStatic) {
+    this.element = $(selector);
+    this.onGoingEvent = undefined;
+  }
+
+  private displayAndFadeout(msg: string, statusType: string, handlers?: Handlers): void {
+    if (this.onGoingEvent)
+      this.onGoingEvent.interrupt();
+
+    this.onGoingEvent = new NotificationEvent(this.element, msg, statusType);
+    this.onGoingEvent.start(handlers);
+  }
+
+  success(msg: string, handlers?: Handlers) {
+    this.displayAndFadeout(msg, "success", handlers);
+  }
+
+  error(msg: string, handlers?: Handlers) {
+    this.displayAndFadeout(msg, "error", handlers);
+  }
+
+  warning(msg: string, handlers?: Handlers) {
+    this.displayAndFadeout(msg, "warning", handlers);
+  }
+
+  info(msg: string, handlers?: Handlers) {
+    this.displayAndFadeout(msg, "info", handlers);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/client/src/lib/QueryParametersRetriever.ts b/themes/triangles/client/src/lib/QueryParametersRetriever.ts
new file mode 100644
index 00000000..a529adb6
--- /dev/null
+++ b/themes/triangles/client/src/lib/QueryParametersRetriever.ts
@@ -0,0 +1,12 @@
+
+export class QueryParametersRetriever {
+  static get(name: string, url?: string): string {
+    if (!url) url = window.location.href;
+    name = name.replace(/[\[\]]/g, "\\$&");
+    const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
+      results = regex.exec(url);
+    if (!results) return undefined;
+    if (!results[2]) return "";
+    return decodeURIComponent(results[2].replace(/\+/g, " "));
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/client/src/lib/SafeRedirect.ts b/themes/triangles/client/src/lib/SafeRedirect.ts
new file mode 100644
index 00000000..7e7684b8
--- /dev/null
+++ b/themes/triangles/client/src/lib/SafeRedirect.ts
@@ -0,0 +1,10 @@
+import { BelongToDomain } from "../../../shared/BelongToDomain";
+
+export function SafeRedirect(url: string, cb: () => void): void {
+  const domain = window.location.hostname.split(".").slice(-2).join(".");
+  if (url.startsWith("/") || BelongToDomain(url, domain)) {
+    window.location.href = url;
+    return;
+  }
+  cb();
+}
\ No newline at end of file
diff --git a/themes/triangles/client/src/lib/firstfactor/FirstFactorValidator.ts b/themes/triangles/client/src/lib/firstfactor/FirstFactorValidator.ts
new file mode 100644
index 00000000..eaa496fd
--- /dev/null
+++ b/themes/triangles/client/src/lib/firstfactor/FirstFactorValidator.ts
@@ -0,0 +1,46 @@
+
+import BluebirdPromise = require("bluebird");
+import Endpoints = require("../../../../shared/api");
+import Constants = require("../../../../shared/constants");
+import Util = require("util");
+import UserMessages = require("../../../../shared/UserMessages");
+
+export function validate(username: string, password: string,
+  keepMeLoggedIn: boolean, redirectUrl: string, $: JQueryStatic)
+  : BluebirdPromise<string> {
+  return new BluebirdPromise<string>(function (resolve, reject) {
+    let url: string;
+    if (redirectUrl != undefined) {
+      const redirectParam = Util.format("%s=%s", Constants.REDIRECT_QUERY_PARAM, redirectUrl);
+      url = Util.format("%s?%s", Endpoints.FIRST_FACTOR_POST, redirectParam);
+    }
+    else {
+      url = Util.format("%s", Endpoints.FIRST_FACTOR_POST);
+    }
+
+    const data: any = {
+      username: username,
+      password: password,
+    };
+
+    if (keepMeLoggedIn) {
+      data.keepMeLoggedIn = "true";
+    }
+
+    $.ajax({
+      method: "POST",
+      url: url,
+      data: data
+    })
+      .done(function (body: any) {
+        if (body && body.error) {
+          reject(new Error(body.error));
+          return;
+        }
+        resolve(body.redirect);
+      })
+      .fail(function (xhr: JQueryXHR, textStatus: string) {
+        reject(new Error(UserMessages.AUTHENTICATION_FAILED));
+      });
+  });
+}
diff --git a/themes/triangles/client/src/lib/firstfactor/UISelectors.ts b/themes/triangles/client/src/lib/firstfactor/UISelectors.ts
new file mode 100644
index 00000000..0e971b3c
--- /dev/null
+++ b/themes/triangles/client/src/lib/firstfactor/UISelectors.ts
@@ -0,0 +1,5 @@
+
+export const USERNAME_FIELD_ID = "#username";
+export const PASSWORD_FIELD_ID = "#password";
+export const SIGN_IN_BUTTON_ID = "#signin";
+export const KEEP_ME_LOGGED_IN_ID = "#keep_me_logged_in";
diff --git a/themes/triangles/client/src/lib/firstfactor/index.ts b/themes/triangles/client/src/lib/firstfactor/index.ts
new file mode 100644
index 00000000..24affee2
--- /dev/null
+++ b/themes/triangles/client/src/lib/firstfactor/index.ts
@@ -0,0 +1,49 @@
+import FirstFactorValidator = require("./FirstFactorValidator");
+import JSLogger = require("js-logger");
+import UISelectors = require("./UISelectors");
+import { Notifier } from "../Notifier";
+import { QueryParametersRetriever } from "../QueryParametersRetriever";
+import Constants = require("../../../../shared/constants");
+import Endpoints = require("../../../../shared/api");
+import UserMessages = require("../../../../shared/UserMessages");
+import { SafeRedirect } from "../SafeRedirect";
+
+export default function (window: Window, $: JQueryStatic,
+  firstFactorValidator: typeof FirstFactorValidator, jslogger: typeof JSLogger) {
+
+  const notifier = new Notifier(".notification", $);
+
+  function onFormSubmitted() {
+    const username: string = $(UISelectors.USERNAME_FIELD_ID).val() as string;
+    const password: string = $(UISelectors.PASSWORD_FIELD_ID).val() as string;
+    const keepMeLoggedIn: boolean = $(UISelectors.KEEP_ME_LOGGED_IN_ID).is(":checked");
+
+    $("form").css("opacity", 0.5);
+    $("input,button").attr("disabled", "true");
+    $(UISelectors.SIGN_IN_BUTTON_ID).text("Please wait...");
+
+    const redirectUrl = QueryParametersRetriever.get(Constants.REDIRECT_QUERY_PARAM);
+    firstFactorValidator.validate(username, password, keepMeLoggedIn, redirectUrl, $)
+      .then(onFirstFactorSuccess, onFirstFactorFailure);
+    return false;
+  }
+
+  function onFirstFactorSuccess(redirectUrl: string) {
+    SafeRedirect(redirectUrl, () => {
+      notifier.error("Cannot redirect to an external domain.");
+    });
+  }
+
+  function onFirstFactorFailure(err: Error) {
+    $("input,button").removeAttr("disabled");
+    $("form").css("opacity", 1);
+    notifier.error(UserMessages.AUTHENTICATION_FAILED);
+    $(UISelectors.PASSWORD_FIELD_ID).select();
+    $(UISelectors.SIGN_IN_BUTTON_ID).text("Sign in");
+  }
+
+  $(window.document).ready(function () {
+    $("form").on("submit", onFormSubmitted);
+  });
+}
+
diff --git a/themes/triangles/client/src/lib/reset-password/constants.ts b/themes/triangles/client/src/lib/reset-password/constants.ts
new file mode 100644
index 00000000..d48d4e67
--- /dev/null
+++ b/themes/triangles/client/src/lib/reset-password/constants.ts
@@ -0,0 +1,2 @@
+
+export const FORM_SELECTOR = ".form-signin";
\ No newline at end of file
diff --git a/themes/triangles/client/src/lib/reset-password/reset-password-form.ts b/themes/triangles/client/src/lib/reset-password/reset-password-form.ts
new file mode 100644
index 00000000..b94279cd
--- /dev/null
+++ b/themes/triangles/client/src/lib/reset-password/reset-password-form.ts
@@ -0,0 +1,57 @@
+import BluebirdPromise = require("bluebird");
+
+import Endpoints = require("../../../../shared/api");
+import UserMessages = require("../../../../shared/UserMessages");
+
+import Constants = require("./constants");
+import { Notifier } from "../Notifier";
+
+export default function (window: Window, $: JQueryStatic) {
+  const notifier = new Notifier(".notification", $);
+
+  function modifyPassword(newPassword: string) {
+    return new BluebirdPromise(function (resolve, reject) {
+      $.post(Endpoints.RESET_PASSWORD_FORM_POST, {
+        password: newPassword,
+      })
+        .done(function (body: any) {
+          if (body && body.error) {
+            reject(new Error(body.error));
+            return;
+          }
+          resolve(body);
+        })
+        .fail(function (xhr, status) {
+          reject(status);
+        });
+    });
+  }
+
+  function onFormSubmitted() {
+    const password1 = $("#password1").val() as string;
+    const password2 = $("#password2").val() as string;
+
+    if (!password1 || !password2) {
+      notifier.warning(UserMessages.MISSING_PASSWORD);
+      return false;
+    }
+
+    if (password1 != password2) {
+      notifier.warning(UserMessages.DIFFERENT_PASSWORDS);
+      return false;
+    }
+
+    modifyPassword(password1)
+      .then(function () {
+        window.location.href = Endpoints.FIRST_FACTOR_GET;
+      })
+      .error(function () {
+        notifier.error(UserMessages.RESET_PASSWORD_FAILED);
+      });
+    return false;
+  }
+
+  $(document).ready(function () {
+    $(Constants.FORM_SELECTOR).on("submit", onFormSubmitted);
+  });
+}
diff --git a/themes/triangles/client/src/lib/reset-password/reset-password-request.ts b/themes/triangles/client/src/lib/reset-password/reset-password-request.ts
new file mode 100644
index 00000000..846226d7
--- /dev/null
+++ b/themes/triangles/client/src/lib/reset-password/reset-password-request.ts
@@ -0,0 +1,56 @@
+
+import BluebirdPromise = require("bluebird");
+
+import Endpoints = require("../../../../shared/api");
+import UserMessages = require("../../../../shared/UserMessages");
+import Constants = require("./constants");
+import jslogger = require("js-logger");
+import { Notifier } from "../Notifier";
+
+export default function (window: Window, $: JQueryStatic) {
+  const notifier = new Notifier(".notification", $);
+
+  function requestPasswordReset(username: string) {
+    return new BluebirdPromise(function (resolve, reject) {
+      $.get(Endpoints.RESET_PASSWORD_IDENTITY_START_GET, {
+        userid: username,
+      })
+        .done(function (body: any) {
+          if (body && body.error) {
+            reject(new Error(body.error));
+            return;
+          }
+          resolve();
+        })
+        .fail(function (xhr: JQueryXHR, textStatus: string) {
+          reject(new Error(textStatus));
+        });
+    });
+  }
+
+  function onFormSubmitted() {
+    const username = $("#username").val() as string;
+
+    if (!username) {
+      notifier.warning(UserMessages.MISSING_USERNAME);
+      return;
+    }
+
+    requestPasswordReset(username)
+      .then(function () {
+        notifier.success(UserMessages.MAIL_SENT);
+        setTimeout(function () {
+          window.location.replace(Endpoints.FIRST_FACTOR_GET);
+        }, 1000);
+      })
+      .error(function () {
+        notifier.error(UserMessages.MAIL_NOT_SENT);
+      });
+    return false;
+  }
+
+  $(document).ready(function () {
+    $(Constants.FORM_SELECTOR).on("submit", onFormSubmitted);
+  });
+}
+
diff --git a/themes/triangles/client/src/lib/secondfactor/TOTPValidator.ts b/themes/triangles/client/src/lib/secondfactor/TOTPValidator.ts
new file mode 100644
index 00000000..5394139a
--- /dev/null
+++ b/themes/triangles/client/src/lib/secondfactor/TOTPValidator.ts
@@ -0,0 +1,28 @@
+
+import BluebirdPromise = require("bluebird");
+import Endpoints = require("../../../../shared/api");
+import { RedirectionMessage } from "../../../../shared/RedirectionMessage";
+import { ErrorMessage } from "../../../../shared/ErrorMessage";
+
+export function validate(token: string, $: JQueryStatic): BluebirdPromise<string> {
+  return new BluebirdPromise<string>(function (resolve, reject) {
+    $.ajax({
+      url: Endpoints.SECOND_FACTOR_TOTP_POST,
+      data: {
+        token: token,
+      },
+      method: "POST",
+      dataType: "json"
+    } as JQueryAjaxSettings)
+      .done(function (body: RedirectionMessage | ErrorMessage) {
+        if (body && "error" in body) {
+          reject(new Error((body as ErrorMessage).error));
+          return;
+        }
+        resolve((body as RedirectionMessage).redirect);
+      })
+      .fail(function (xhr: JQueryXHR, textStatus: string) {
+        reject(new Error(textStatus));
+      });
+  });
+}
\ No newline at end of file
diff --git a/themes/triangles/client/src/lib/secondfactor/U2FValidator.ts b/themes/triangles/client/src/lib/secondfactor/U2FValidator.ts
new file mode 100644
index 00000000..5812922f
--- /dev/null
+++ b/themes/triangles/client/src/lib/secondfactor/U2FValidator.ts
@@ -0,0 +1,42 @@
+import U2f = require("u2f");
+import U2fApi from "u2f-api";
+import BluebirdPromise = require("bluebird");
+import Endpoints = require("../../../../shared/api");
+import UserMessages = require("../../../../shared/UserMessages");
+import { INotifier } from "../INotifier";
+import { RedirectionMessage } from "../../../../shared/RedirectionMessage";
+import { ErrorMessage } from "../../../../shared/ErrorMessage";
+import GetPromised from "../GetPromised";
+
+function finishU2fAuthentication(responseData: U2fApi.SignResponse,
+  $: JQueryStatic): BluebirdPromise<string> {
+  return new BluebirdPromise<string>(function (resolve, reject) {
+    $.ajax({
+      url: Endpoints.SECOND_FACTOR_U2F_SIGN_POST,
+      data: responseData,
+      method: "POST",
+      dataType: "json"
+    } as JQueryAjaxSettings)
+      .done(function (body: RedirectionMessage | ErrorMessage) {
+        if (body && "error" in body) {
+          reject(new Error((body as ErrorMessage).error));
+          return;
+        }
+        resolve((body as RedirectionMessage).redirect);
+      })
+      .fail(function (xhr: JQueryXHR, textStatus: string) {
+        reject(new Error(textStatus));
+      });
+  });
+}
+
+export function validate($: JQueryStatic): BluebirdPromise<string> {
+  return GetPromised($, Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, {},
+    undefined, "json")
+    .then(function (signRequest: U2f.Request) {
+      return U2fApi.sign(signRequest, 60);
+    })
+    .then(function (signResponse: U2fApi.SignResponse) {
+      return finishU2fAuthentication(signResponse, $);
+    });
+}
diff --git a/themes/triangles/client/src/lib/secondfactor/constants.ts b/themes/triangles/client/src/lib/secondfactor/constants.ts
new file mode 100644
index 00000000..50bba757
--- /dev/null
+++ b/themes/triangles/client/src/lib/secondfactor/constants.ts
@@ -0,0 +1,3 @@
+
+export const TOTP_FORM_SELECTOR = ".form-signin.totp";
+export const TOTP_TOKEN_SELECTOR = ".form-signin #token";
diff --git a/themes/triangles/client/src/lib/secondfactor/index.ts b/themes/triangles/client/src/lib/secondfactor/index.ts
new file mode 100644
index 00000000..279723dc
--- /dev/null
+++ b/themes/triangles/client/src/lib/secondfactor/index.ts
@@ -0,0 +1,59 @@
+import TOTPValidator = require("./TOTPValidator");
+import U2FValidator = require("./U2FValidator");
+import ClientConstants = require("./constants");
+import { Notifier } from "../Notifier";
+import { QueryParametersRetriever } from "../QueryParametersRetriever";
+import UserMessages = require("../../../../shared/UserMessages");
+import SharedConstants = require("../../../../shared/constants");
+import { SafeRedirect } from "../SafeRedirect";
+
+export default function (window: Window, $: JQueryStatic) {
+  const notifier = new Notifier(".notification", $);
+
+  function onAuthenticationSuccess(serverRedirectUrl: string) {
+    const queryRedirectUrl = QueryParametersRetriever.get(SharedConstants.REDIRECT_QUERY_PARAM);
+    if (queryRedirectUrl) {
+      SafeRedirect(queryRedirectUrl, () => {
+        notifier.error(UserMessages.CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN);
+      });
+    } else if (serverRedirectUrl) {
+      SafeRedirect(serverRedirectUrl, () => {
+        notifier.error(UserMessages.CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN);
+      });
+    } else {
+      notifier.success(UserMessages.AUTHENTICATION_SUCCEEDED);
+    }
+  }
+
+  function onSecondFactorTotpSuccess(redirectUrl: string) {
+    onAuthenticationSuccess(redirectUrl);
+  }
+
+  function onSecondFactorTotpFailure(err: Error) {
+    notifier.error(UserMessages.AUTHENTICATION_TOTP_FAILED);
+  }
+
+  function onU2fAuthenticationSuccess(redirectUrl: string) {
+    onAuthenticationSuccess(redirectUrl);
+  }
+
+  function onU2fAuthenticationFailure() {
+    // TODO(clems4ever): we should not display this error message until a device
+    //                   is registered.
+    // notifier.error(UserMessages.AUTHENTICATION_U2F_FAILED);
+  }
+
+  function onTOTPFormSubmitted(): boolean {
+    const token = $(ClientConstants.TOTP_TOKEN_SELECTOR).val() as string;
+    TOTPValidator.validate(token, $)
+      .then(onSecondFactorTotpSuccess)
+      .catch(onSecondFactorTotpFailure);
+    return false;
+  }
+
+  $(window.document).ready(function () {
+    $(ClientConstants.TOTP_FORM_SELECTOR).on("submit", onTOTPFormSubmitted);
+    U2FValidator.validate($)
+      .then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure);
+  });
+}
\ No newline at end of file
diff --git a/themes/triangles/client/src/lib/totp-register/totp-register.ts b/themes/triangles/client/src/lib/totp-register/totp-register.ts
new file mode 100644
index 00000000..6a9aa7ee
--- /dev/null
+++ b/themes/triangles/client/src/lib/totp-register/totp-register.ts
@@ -0,0 +1,11 @@
+
+import jslogger = require("js-logger");
+import UISelector = require("./ui-selector");
+
+export default function(window: Window, $: JQueryStatic) {
+  jslogger.debug("Creating QRCode from OTPAuth url");
+  const qrcode = $(UISelector.QRCODE_ID_SELECTOR);
+  const val = qrcode.text();
+  qrcode.empty();
+  new (window as any).QRCode(qrcode.get(0), val);
+}
diff --git a/themes/triangles/client/src/lib/totp-register/ui-selector.ts b/themes/triangles/client/src/lib/totp-register/ui-selector.ts
new file mode 100644
index 00000000..9d43fabe
--- /dev/null
+++ b/themes/triangles/client/src/lib/totp-register/ui-selector.ts
@@ -0,0 +1,2 @@
+
+export const QRCODE_ID_SELECTOR = "#qrcode";
\ No newline at end of file
diff --git a/themes/triangles/client/src/lib/u2f-register/u2f-register.ts b/themes/triangles/client/src/lib/u2f-register/u2f-register.ts
new file mode 100644
index 00000000..abf40ee0
--- /dev/null
+++ b/themes/triangles/client/src/lib/u2f-register/u2f-register.ts
@@ -0,0 +1,56 @@
+
+import BluebirdPromise = require("bluebird");
+import U2f = require("u2f");
+import * as U2fApi from "u2f-api";
+import { Notifier } from "../Notifier";
+import GetPromised from "../GetPromised";
+import Endpoints = require("../../../../shared/api");
+import UserMessages = require("../../../../shared/UserMessages");
+import { RedirectionMessage } from "../../../../shared/RedirectionMessage";
+import { ErrorMessage } from "../../../../shared/ErrorMessage";
+import { SafeRedirect } from "../SafeRedirect";
+
+export default function (window: Window, $: JQueryStatic) {
+  const notifier = new Notifier(".notification", $);
+
+  function checkRegistration(regResponse: U2fApi.RegisterResponse): BluebirdPromise<string> {
+    return new BluebirdPromise<string>(function (resolve, reject) {
+      $.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, regResponse, undefined, "json")
+        .done((body: RedirectionMessage | ErrorMessage) => {
+          if (body && "error" in body) {
+            reject(new Error((body as ErrorMessage).error));
+            return;
+          }
+          resolve((body as RedirectionMessage).redirect);
+        })
+        .fail((xhr, status) => {
+          reject(new Error("Failed to register device."));
+        });
+    });
+  }
+
+  function requestRegistration(): BluebirdPromise<string> {
+    return GetPromised($, Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, {},
+      undefined, "json")
+      .then((registrationRequest: U2f.Request) => {
+        return U2fApi.register(registrationRequest, [], 60);
+      })
+      .then((res) => checkRegistration(res));
+  }
+
+  function onRegisterFailure(err: Error) {
+    notifier.error(UserMessages.REGISTRATION_U2F_FAILED);
+  }
+
+  $(document).ready(function () {
+    requestRegistration()
+      .then((redirectionUrl: string) => {
+        SafeRedirect(redirectionUrl, () => {
+          notifier.error(UserMessages.CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN);
+        });
+      })
+      .catch((err) => {
+        onRegisterFailure(err);
+      });
+  });
+}
diff --git a/themes/triangles/client/src/thirdparties/qrcode.min.js b/themes/triangles/client/src/thirdparties/qrcode.min.js
new file mode 100644
index 00000000..993e88f3
--- /dev/null
+++ b/themes/triangles/client/src/thirdparties/qrcode.min.js
@@ -0,0 +1 @@
+var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c<a.length&&0==a[c];)c++;this.num=new Array(a.length-c+b);for(var d=0;d<a.length-c;d++)this.num[d]=a[d+c]}function j(a,b){this.totalCount=a,this.dataCount=b}function k(){this.buffer=[],this.length=0}function m(){return"undefined"!=typeof CanvasRenderingContext2D}function n(){var a=!1,b=navigator.userAgent;return/android/i.test(b)&&(a=!0,aMat=b.toString().match(/android ([0-9]\.[0-9])/i),aMat&&aMat[1]&&(a=parseFloat(aMat[1]))),a}function r(a,b){for(var c=1,e=s(a),f=0,g=l.length;g>=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=new Array(this.moduleCount);for(var e=0;e<this.moduleCount;e++)this.modules[d][e]=null}this.setupPositionProbePattern(0,0),this.setupPositionProbePattern(this.moduleCount-7,0),this.setupPositionProbePattern(0,this.moduleCount-7),this.setupPositionAdjustPattern(),this.setupTimingPattern(),this.setupTypeInfo(a,c),this.typeNumber>=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f<this.modules.length;f++)for(var g=f*e,h=0;h<this.modules[f].length;h++){var i=h*e,j=this.modules[f][h];j&&(d.beginFill(0,100),d.moveTo(i,g),d.lineTo(i+e,g),d.lineTo(i+e,g+e),d.lineTo(i,g+e),d.endFill())}return d},setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(var b=8;b<this.moduleCount-8;b++)null==this.modules[6][b]&&(this.modules[6][b]=0==b%2)},setupPositionAdjustPattern:function(){for(var a=f.getPatternPosition(this.typeNumber),b=0;b<a.length;b++)for(var c=0;c<a.length;c++){var d=a[b],e=a[c];if(null==this.modules[d][e])for(var g=-2;2>=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g<a.length&&(j=1==(1&a[g]>>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h<d.length;h++){var i=d[h];g.put(i.mode,4),g.put(i.getLength(),f.getLengthInBits(i.mode,a)),i.write(g)}for(var l=0,h=0;h<e.length;h++)l+=e[h].dataCount;if(g.getLengthInBits()>8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j<b.length;j++){var k=b[j].dataCount,l=b[j].totalCount-k;d=Math.max(d,k),e=Math.max(e,l),g[j]=new Array(k);for(var m=0;m<g[j].length;m++)g[j][m]=255&a.buffer[m+c];c+=k;var n=f.getErrorCorrectPolynomial(l),o=new i(g[j],n.getLength()-1),p=o.mod(n);h[j]=new Array(n.getLength()-1);for(var m=0;m<h[j].length;m++){var q=m+p.getLength()-h[j].length;h[j][m]=q>=0?p.get(q):0}}for(var r=0,m=0;m<b.length;m++)r+=b[m].totalCount;for(var s=new Array(r),t=0,m=0;d>m;m++)for(var j=0;j<b.length;j++)m<g[j].length&&(s[t++]=g[j][m]);for(var m=0;e>m;m++)for(var j=0;j<b.length;j++)m<h[j].length&&(s[t++]=h[j][m]);return s};for(var c={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},d={L:1,M:0,Q:3,H:2},e={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},f={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(a){for(var b=a<<10;f.getBCHDigit(b)-f.getBCHDigit(f.G15)>=0;)b^=f.G15<<f.getBCHDigit(b)-f.getBCHDigit(f.G15);return(a<<10|b)^f.G15_MASK},getBCHTypeNumber:function(a){for(var b=a<<12;f.getBCHDigit(b)-f.getBCHDigit(f.G18)>=0;)b^=f.G18<<f.getBCHDigit(b)-f.getBCHDigit(f.G18);return a<<12|b},getBCHDigit:function(a){for(var b=0;0!=a;)b++,a>>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<<h;for(var h=8;256>h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;c<this.getLength();c++)for(var d=0;d<a.getLength();d++)b[c+d]^=g.gexp(g.glog(this.get(c))+g.glog(a.get(d)));return new i(b,0)},mod:function(a){if(this.getLength()-a.getLength()<0)return this;for(var b=g.glog(this.get(0))-g.glog(a.get(0)),c=new Array(this.getLength()),d=0;d<this.getLength();d++)c[d]=this.get(d);for(var d=0;d<a.getLength();d++)c[d]^=g.gexp(g.glog(a.get(d))+b);return new i(c,0).mod(a)}},j.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],j.getRSBlocks=function(a,b){var c=j.getRsBlockTable(a,b);if(void 0==c)throw new Error("bad rs block @ typeNumber:"+a+"/errorCorrectLevel:"+b);for(var d=c.length/3,e=[],f=0;d>f;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=['<table style="border:0;border-collapse:collapse;">'],h=0;d>h;h++){g.push("<tr>");for(var i=0;d>i;i++)g.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:'+e+"px;height:"+f+"px;background-color:"+(a.isDark(h,i)?b.colorDark:b.colorLight)+';"></td>');g.push("</tr>")}g.push("</table>"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}();
\ No newline at end of file
diff --git a/themes/triangles/client/src/thirdparties/u2f-api.js b/themes/triangles/client/src/thirdparties/u2f-api.js
new file mode 100644
index 00000000..8c7801e3
--- /dev/null
+++ b/themes/triangles/client/src/thirdparties/u2f-api.js
@@ -0,0 +1,749 @@
+//Copyright 2014-2015 Google Inc. All rights reserved.
+
+//Use of this source code is governed by a BSD-style
+//license that can be found in the LICENSE file or at
+//https://developers.google.com/open-source/licenses/bsd
+
+/**
+ * @fileoverview The U2F api.
+ */
+'use strict';
+
+
+/**
+ * Namespace for the U2F api.
+ * @type {Object}
+ */
+var u2f = u2f || {};
+
+/**
+ * FIDO U2F Javascript API Version
+ * @number
+ */
+var js_api_version;
+
+/**
+ * The U2F extension id
+ * @const {string}
+ */
+// The Chrome packaged app extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the package Chrome app and does not require installing the U2F Chrome extension.
+ u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
+// The U2F Chrome extension ID.
+// Uncomment this if you want to deploy a server instance that uses
+// the U2F Chrome extension to authenticate.
+// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
+
+
+/**
+ * Message types for messsages to/from the extension
+ * @const
+ * @enum {string}
+ */
+u2f.MessageTypes = {
+    'U2F_REGISTER_REQUEST': 'u2f_register_request',
+    'U2F_REGISTER_RESPONSE': 'u2f_register_response',
+    'U2F_SIGN_REQUEST': 'u2f_sign_request',
+    'U2F_SIGN_RESPONSE': 'u2f_sign_response',
+    'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
+    'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
+};
+
+
+/**
+ * Response status codes
+ * @const
+ * @enum {number}
+ */
+u2f.ErrorCodes = {
+    'OK': 0,
+    'OTHER_ERROR': 1,
+    'BAD_REQUEST': 2,
+    'CONFIGURATION_UNSUPPORTED': 3,
+    'DEVICE_INELIGIBLE': 4,
+    'TIMEOUT': 5
+};
+
+
+/**
+ * A message for registration requests
+ * @typedef {{
+ *   type: u2f.MessageTypes,
+ *   appId: ?string,
+ *   timeoutSeconds: ?number,
+ *   requestId: ?number
+ * }}
+ */
+u2f.U2fRequest;
+
+
+/**
+ * A message for registration responses
+ * @typedef {{
+ *   type: u2f.MessageTypes,
+ *   responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
+ *   requestId: ?number
+ * }}
+ */
+u2f.U2fResponse;
+
+
+/**
+ * An error object for responses
+ * @typedef {{
+ *   errorCode: u2f.ErrorCodes,
+ *   errorMessage: ?string
+ * }}
+ */
+u2f.Error;
+
+/**
+ * Data object for a single sign request.
+ * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
+ */
+u2f.Transport;
+
+
+/**
+ * Data object for a single sign request.
+ * @typedef {Array<u2f.Transport>}
+ */
+u2f.Transports;
+
+/**
+ * Data object for a single sign request.
+ * @typedef {{
+ *   version: string,
+ *   challenge: string,
+ *   keyHandle: string,
+ *   appId: string
+ * }}
+ */
+u2f.SignRequest;
+
+
+/**
+ * Data object for a sign response.
+ * @typedef {{
+ *   keyHandle: string,
+ *   signatureData: string,
+ *   clientData: string
+ * }}
+ */
+u2f.SignResponse;
+
+
+/**
+ * Data object for a registration request.
+ * @typedef {{
+ *   version: string,
+ *   challenge: string
+ * }}
+ */
+u2f.RegisterRequest;
+
+
+/**
+ * Data object for a registration response.
+ * @typedef {{
+ *   version: string,
+ *   keyHandle: string,
+ *   transports: Transports,
+ *   appId: string
+ * }}
+ */
+u2f.RegisterResponse;
+
+
+/**
+ * Data object for a registered key.
+ * @typedef {{
+ *   version: string,
+ *   keyHandle: string,
+ *   transports: ?Transports,
+ *   appId: ?string
+ * }}
+ */
+u2f.RegisteredKey;
+
+
+/**
+ * Data object for a get API register response.
+ * @typedef {{
+ *   js_api_version: number
+ * }}
+ */
+u2f.GetJsApiVersionResponse;
+
+
+//Low level MessagePort API support
+
+/**
+ * Sets up a MessagePort to the U2F extension using the
+ * available mechanisms.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ */
+u2f.getMessagePort = function(callback) {
+  if (typeof chrome != 'undefined' && chrome.runtime) {
+    // The actual message here does not matter, but we need to get a reply
+    // for the callback to run. Thus, send an empty signature request
+    // in order to get a failure response.
+    var msg = {
+        type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+        signRequests: []
+    };
+    chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
+      if (!chrome.runtime.lastError) {
+        // We are on a whitelisted origin and can talk directly
+        // with the extension.
+        u2f.getChromeRuntimePort_(callback);
+      } else {
+        // chrome.runtime was available, but we couldn't message
+        // the extension directly, use iframe
+        u2f.getIframePort_(callback);
+      }
+    });
+  } else if (u2f.isAndroidChrome_()) {
+    u2f.getAuthenticatorPort_(callback);
+  } else if (u2f.isIosChrome_()) {
+    u2f.getIosPort_(callback);
+  } else {
+    // chrome.runtime was not available at all, which is normal
+    // when this origin doesn't have access to any extensions.
+    u2f.getIframePort_(callback);
+  }
+};
+
+/**
+ * Detect chrome running on android based on the browser's useragent.
+ * @private
+ */
+u2f.isAndroidChrome_ = function() {
+  var userAgent = navigator.userAgent;
+  return userAgent.indexOf('Chrome') != -1 &&
+  userAgent.indexOf('Android') != -1;
+};
+
+/**
+ * Detect chrome running on iOS based on the browser's platform.
+ * @private
+ */
+u2f.isIosChrome_ = function() {
+  return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
+};
+
+/**
+ * Connects directly to the extension via chrome.runtime.connect.
+ * @param {function(u2f.WrappedChromeRuntimePort_)} callback
+ * @private
+ */
+u2f.getChromeRuntimePort_ = function(callback) {
+  var port = chrome.runtime.connect(u2f.EXTENSION_ID,
+      {'includeTlsChannelId': true});
+  setTimeout(function() {
+    callback(new u2f.WrappedChromeRuntimePort_(port));
+  }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the Authenticator app.
+ * @param {function(u2f.WrappedAuthenticatorPort_)} callback
+ * @private
+ */
+u2f.getAuthenticatorPort_ = function(callback) {
+  setTimeout(function() {
+    callback(new u2f.WrappedAuthenticatorPort_());
+  }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the iOS client app.
+ * @param {function(u2f.WrappedIosPort_)} callback
+ * @private
+ */
+u2f.getIosPort_ = function(callback) {
+  setTimeout(function() {
+    callback(new u2f.WrappedIosPort_());
+  }, 0);
+};
+
+/**
+ * A wrapper for chrome.runtime.Port that is compatible with MessagePort.
+ * @param {Port} port
+ * @constructor
+ * @private
+ */
+u2f.WrappedChromeRuntimePort_ = function(port) {
+  this.port_ = port;
+};
+
+/**
+ * Format and return a sign request compliant with the JS API version supported by the extension.
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.formatSignRequest_ =
+  function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
+  if (js_api_version === undefined || js_api_version < 1.1) {
+    // Adapt request to the 1.0 JS API
+    var signRequests = [];
+    for (var i = 0; i < registeredKeys.length; i++) {
+      signRequests[i] = {
+          version: registeredKeys[i].version,
+          challenge: challenge,
+          keyHandle: registeredKeys[i].keyHandle,
+          appId: appId
+      };
+    }
+    return {
+      type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+      signRequests: signRequests,
+      timeoutSeconds: timeoutSeconds,
+      requestId: reqId
+    };
+  }
+  // JS 1.1 API
+  return {
+    type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+    appId: appId,
+    challenge: challenge,
+    registeredKeys: registeredKeys,
+    timeoutSeconds: timeoutSeconds,
+    requestId: reqId
+  };
+};
+
+/**
+ * Format and return a register request compliant with the JS API version supported by the extension..
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {Array<u2f.RegisterRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.formatRegisterRequest_ =
+  function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
+  if (js_api_version === undefined || js_api_version < 1.1) {
+    // Adapt request to the 1.0 JS API
+    for (var i = 0; i < registerRequests.length; i++) {
+      registerRequests[i].appId = appId;
+    }
+    var signRequests = [];
+    for (var i = 0; i < registeredKeys.length; i++) {
+      signRequests[i] = {
+          version: registeredKeys[i].version,
+          challenge: registerRequests[0],
+          keyHandle: registeredKeys[i].keyHandle,
+          appId: appId
+      };
+    }
+    return {
+      type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+      signRequests: signRequests,
+      registerRequests: registerRequests,
+      timeoutSeconds: timeoutSeconds,
+      requestId: reqId
+    };
+  }
+  // JS 1.1 API
+  return {
+    type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+    appId: appId,
+    registerRequests: registerRequests,
+    registeredKeys: registeredKeys,
+    timeoutSeconds: timeoutSeconds,
+    requestId: reqId
+  };
+};
+
+
+/**
+ * Posts a message on the underlying channel.
+ * @param {Object} message
+ */
+u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
+  this.port_.postMessage(message);
+};
+
+
+/**
+ * Emulates the HTML 5 addEventListener interface. Works only for the
+ * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
+    function(eventName, handler) {
+  var name = eventName.toLowerCase();
+  if (name == 'message' || name == 'onmessage') {
+    this.port_.onMessage.addListener(function(message) {
+      // Emulate a minimal MessageEvent object
+      handler({'data': message});
+    });
+  } else {
+    console.error('WrappedChromeRuntimePort only supports onMessage');
+  }
+};
+
+/**
+ * Wrap the Authenticator app with a MessagePort interface.
+ * @constructor
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_ = function() {
+  this.requestId_ = -1;
+  this.requestObject_ = null;
+}
+
+/**
+ * Launch the Authenticator intent.
+ * @param {Object} message
+ */
+u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
+  var intentUrl =
+    u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
+    ';S.request=' + encodeURIComponent(JSON.stringify(message)) +
+    ';end';
+  document.location = intentUrl;
+};
+
+/**
+ * Tells what type of port this is.
+ * @return {String} port type
+ */
+u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
+  return "WrappedAuthenticatorPort_";
+};
+
+
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) {
+  var name = eventName.toLowerCase();
+  if (name == 'message') {
+    var self = this;
+    /* Register a callback to that executes when
+     * chrome injects the response. */
+    window.addEventListener(
+        'message', self.onRequestUpdate_.bind(self, handler), false);
+  } else {
+    console.error('WrappedAuthenticatorPort only supports message');
+  }
+};
+
+/**
+ * Callback invoked  when a response is received from the Authenticator.
+ * @param function({data: Object}) callback
+ * @param {Object} message message Object
+ */
+u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
+    function(callback, message) {
+  var messageObject = JSON.parse(message.data);
+  var intentUrl = messageObject['intentURL'];
+
+  var errorCode = messageObject['errorCode'];
+  var responseObject = null;
+  if (messageObject.hasOwnProperty('data')) {
+    responseObject = /** @type {Object} */ (
+        JSON.parse(messageObject['data']));
+  }
+
+  callback({'data': responseObject});
+};
+
+/**
+ * Base URL for intents to Authenticator.
+ * @const
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
+  'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
+
+/**
+ * Wrap the iOS client app with a MessagePort interface.
+ * @constructor
+ * @private
+ */
+u2f.WrappedIosPort_ = function() {};
+
+/**
+ * Launch the iOS client app request
+ * @param {Object} message
+ */
+u2f.WrappedIosPort_.prototype.postMessage = function(message) {
+  var str = JSON.stringify(message);
+  var url = "u2f://auth?" + encodeURI(str);
+  location.replace(url);
+};
+
+/**
+ * Tells what type of port this is.
+ * @return {String} port type
+ */
+u2f.WrappedIosPort_.prototype.getPortType = function() {
+  return "WrappedIosPort_";
+};
+
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
+  var name = eventName.toLowerCase();
+  if (name !== 'message') {
+    console.error('WrappedIosPort only supports message');
+  }
+};
+
+/**
+ * Sets up an embedded trampoline iframe, sourced from the extension.
+ * @param {function(MessagePort)} callback
+ * @private
+ */
+u2f.getIframePort_ = function(callback) {
+  // Create the iframe
+  var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
+  var iframe = document.createElement('iframe');
+  iframe.src = iframeOrigin + '/u2f-comms.html';
+  iframe.setAttribute('style', 'display:none');
+  document.body.appendChild(iframe);
+
+  var channel = new MessageChannel();
+  var ready = function(message) {
+    if (message.data == 'ready') {
+      channel.port1.removeEventListener('message', ready);
+      callback(channel.port1);
+    } else {
+      console.error('First event on iframe port was not "ready"');
+    }
+  };
+  channel.port1.addEventListener('message', ready);
+  channel.port1.start();
+
+  iframe.addEventListener('load', function() {
+    // Deliver the port to the iframe and initialize
+    iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
+  });
+};
+
+
+//High-level JS API
+
+/**
+ * Default extension response timeout in seconds.
+ * @const
+ */
+u2f.EXTENSION_TIMEOUT_SEC = 30;
+
+/**
+ * A singleton instance for a MessagePort to the extension.
+ * @type {MessagePort|u2f.WrappedChromeRuntimePort_}
+ * @private
+ */
+u2f.port_ = null;
+
+/**
+ * Callbacks waiting for a port
+ * @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
+ * @private
+ */
+u2f.waitingForPort_ = [];
+
+/**
+ * A counter for requestIds.
+ * @type {number}
+ * @private
+ */
+u2f.reqCounter_ = 0;
+
+/**
+ * A map from requestIds to client callbacks
+ * @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
+ *                       |function((u2f.Error|u2f.SignResponse)))>}
+ * @private
+ */
+u2f.callbackMap_ = {};
+
+/**
+ * Creates or retrieves the MessagePort singleton to use.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ * @private
+ */
+u2f.getPortSingleton_ = function(callback) {
+  if (u2f.port_) {
+    callback(u2f.port_);
+  } else {
+    if (u2f.waitingForPort_.length == 0) {
+      u2f.getMessagePort(function(port) {
+        u2f.port_ = port;
+        u2f.port_.addEventListener('message',
+            /** @type {function(Event)} */ (u2f.responseHandler_));
+
+        // Careful, here be async callbacks. Maybe.
+        while (u2f.waitingForPort_.length)
+          u2f.waitingForPort_.shift()(u2f.port_);
+      });
+    }
+    u2f.waitingForPort_.push(callback);
+  }
+};
+
+/**
+ * Handles response messages from the extension.
+ * @param {MessageEvent.<u2f.Response>} message
+ * @private
+ */
+u2f.responseHandler_ = function(message) {
+  var response = message.data;
+  var reqId = response['requestId'];
+  if (!reqId || !u2f.callbackMap_[reqId]) {
+    console.error('Unknown or missing requestId in response.');
+    return;
+  }
+  var cb = u2f.callbackMap_[reqId];
+  delete u2f.callbackMap_[reqId];
+  cb(response['responseData']);
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the sign request.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+  if (js_api_version === undefined) {
+    // Send a message to get the extension to JS API version, then send the actual sign request.
+    u2f.getApiVersion(
+        function (response) {
+          js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
+          console.log("Extension JS API Version: ", js_api_version);
+          u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+        });
+  } else {
+    // We know the JS API version. Send the actual sign request in the supported API version.
+    u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
+  }
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * @param {string=} appId
+ * @param {string=} challenge
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
+  u2f.getPortSingleton_(function(port) {
+    var reqId = ++u2f.reqCounter_;
+    u2f.callbackMap_[reqId] = callback;
+    var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+        opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+    var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
+    port.postMessage(req);
+  });
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * If the JS API version supported by the extension is unknown, it first sends a
+ * message to the extension to find out the supported API version and then it sends
+ * the register request.
+ * @param {string=} appId
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+  if (js_api_version === undefined) {
+    // Send a message to get the extension to JS API version, then send the actual register request.
+    u2f.getApiVersion(
+        function (response) {
+          js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version'];
+          console.log("Extension JS API Version: ", js_api_version);
+          u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+              callback, opt_timeoutSeconds);
+        });
+  } else {
+    // We know the JS API version. Send the actual register request in the supported API version.
+    u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
+        callback, opt_timeoutSeconds);
+  }
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * @param {string=} appId
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.RegisteredKey>} registeredKeys
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
+  u2f.getPortSingleton_(function(port) {
+    var reqId = ++u2f.reqCounter_;
+    u2f.callbackMap_[reqId] = callback;
+    var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+        opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+    var req = u2f.formatRegisterRequest_(
+        appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
+    port.postMessage(req);
+  });
+};
+
+
+/**
+ * Dispatches a message to the extension to find out the supported
+ * JS API version.
+ * If the user is on a mobile phone and is thus using Google Authenticator instead
+ * of the Chrome extension, don't send the request and simply return 0.
+ * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+   // If we are using Android Google Authenticator or iOS client app,
+   // do not fire an intent to ask which JS API version to use.
+   if (port.getPortType) {
+     var apiVersion;
+     switch (port.getPortType()) {
+       case 'WrappedIosPort_':
+       case 'WrappedAuthenticatorPort_':
+         apiVersion = 1.1;
+         break;
+
+       default:
+         apiVersion = 0;
+         break;
+     }
+     callback({ 'js_api_version': apiVersion });
+     return;
+   }
+    var reqId = ++u2f.reqCounter_;
+    u2f.callbackMap_[reqId] = callback;
+    var req = {
+      type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
+      timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
+          opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
+      requestId: reqId
+    };
+    port.postMessage(req);
+  });
+};
+
diff --git a/themes/triangles/client/test/Notifier.test.ts b/themes/triangles/client/test/Notifier.test.ts
new file mode 100644
index 00000000..70bfea14
--- /dev/null
+++ b/themes/triangles/client/test/Notifier.test.ts
@@ -0,0 +1,71 @@
+
+import Assert = require("assert");
+import Sinon = require("sinon");
+import JQueryMock = require("./mocks/jquery");
+
+import { Notifier } from "../src/lib/Notifier";
+
+describe("test notifier", function() {
+  const SELECTOR = "dummy-selector";
+  const MESSAGE = "This is a message";
+  let jqueryMock: { jquery: JQueryMock.JQueryMock, element: JQueryMock.JQueryElementsMock };
+  let clock: any;
+
+  beforeEach(function() {
+    jqueryMock = JQueryMock.JQueryMock();
+    clock = Sinon.useFakeTimers();
+  });
+
+  afterEach(function() {
+    clock.restore();
+  });
+
+  function should_fade_in_and_out_on_notification(notificationType: string): void {
+    const delayReturn = {
+      fadeOut: Sinon.stub()
+    };
+
+    jqueryMock.element.fadeIn.yields();
+
+    function onFadedInCallback() {
+      Assert(jqueryMock.element.fadeIn.calledOnce);
+      Assert(jqueryMock.element.addClass.calledWith(notificationType));
+      Assert(!jqueryMock.element.removeClass.calledWith(notificationType));
+      clock.tick(10 * 1000);
+    }
+
+    function onFadedOutCallback() {
+      Assert(jqueryMock.element.removeClass.calledWith(notificationType));
+      Assert(jqueryMock.element.fadeOut.calledOnce);
+    }
+
+    const notifier = new Notifier(SELECTOR, jqueryMock.jquery as any);
+
+    // Call the method by its name... Bad but allows code reuse.
+    (notifier as any)[notificationType](MESSAGE, {
+      onFadedIn: onFadedInCallback,
+      onFadedOut: onFadedOutCallback
+    });
+
+    clock.tick(510);
+
+    Assert(jqueryMock.element.fadeIn.calledOnce);
+  }
+
+
+  it("should fade in and fade out an error message", function() {
+    should_fade_in_and_out_on_notification("error");
+  });
+
+  it("should fade in and fade out an info message", function() {
+    should_fade_in_and_out_on_notification("info");
+  });
+
+  it("should fade in and fade out an warning message", function() {
+    should_fade_in_and_out_on_notification("warning");
+  });
+
+  it("should fade in and fade out an success message", function() {
+    should_fade_in_and_out_on_notification("success");
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/client/test/firstfactor/FirstFactorValidator.test.ts b/themes/triangles/client/test/firstfactor/FirstFactorValidator.test.ts
new file mode 100644
index 00000000..ac835327
--- /dev/null
+++ b/themes/triangles/client/test/firstfactor/FirstFactorValidator.test.ts
@@ -0,0 +1,44 @@
+
+import FirstFactorValidator = require("../../src/lib/firstfactor/FirstFactorValidator");
+import JQueryMock = require("../mocks/jquery");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+
+describe("test FirstFactorValidator", function () {
+    it("should validate first factor successfully", () => {
+        const postPromise = JQueryMock.JQueryDeferredMock();
+        postPromise.done.yields({ redirect: "http://redirect" });
+        postPromise.done.returns(postPromise);
+
+        const jqueryMock = JQueryMock.JQueryMock();
+        jqueryMock.jquery.ajax.returns(postPromise);
+
+        return FirstFactorValidator.validate("username", "password", "http://redirect", jqueryMock.jquery as any);
+    });
+
+    function should_fail_first_factor_validation(errorMessage: string) {
+        const xhr = {
+            status: 401
+        };
+        const postPromise = JQueryMock.JQueryDeferredMock();
+        postPromise.fail.yields(xhr, errorMessage);
+        postPromise.done.returns(postPromise);
+
+        const jqueryMock = JQueryMock.JQueryMock();
+        jqueryMock.jquery.ajax.returns(postPromise);
+
+        return FirstFactorValidator.validate("username", "password", "http://redirect", jqueryMock.jquery as any)
+            .then(function () {
+                return BluebirdPromise.reject(new Error("First factor validation successfully finished while it should have not."));
+            }, function (err: Error) {
+                Assert.equal(errorMessage, err.message);
+                return BluebirdPromise.resolve();
+            });
+    }
+
+    describe("should fail first factor validation", () => {
+        it("should fail with error", () => {
+            return should_fail_first_factor_validation("Authentication failed. Please check your credentials.");
+        });
+    });
+});
\ No newline at end of file
diff --git a/themes/triangles/client/test/mocks/NotifierStub.ts b/themes/triangles/client/test/mocks/NotifierStub.ts
new file mode 100644
index 00000000..9c268d66
--- /dev/null
+++ b/themes/triangles/client/test/mocks/NotifierStub.ts
@@ -0,0 +1,33 @@
+
+import Sinon = require("sinon");
+import { INotifier } from "../../src/lib/INotifier";
+
+export class NotifierStub implements INotifier {
+  successStub: Sinon.SinonStub;
+  errorStub: Sinon.SinonStub;
+  warnStub: Sinon.SinonStub;
+  infoStub: Sinon.SinonStub;
+
+  constructor() {
+    this.successStub = Sinon.stub();
+    this.errorStub = Sinon.stub();
+    this.warnStub = Sinon.stub();
+    this.infoStub = Sinon.stub();
+  }
+
+  success(msg: string) {
+    this.successStub();
+  }
+
+  error(msg: string) {
+    this.errorStub();
+  }
+
+  warning(msg: string) {
+    this.warnStub();
+  }
+
+  info(msg: string) {
+    this.infoStub();
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/client/test/mocks/jquery.ts b/themes/triangles/client/test/mocks/jquery.ts
new file mode 100644
index 00000000..273f9086
--- /dev/null
+++ b/themes/triangles/client/test/mocks/jquery.ts
@@ -0,0 +1,59 @@
+
+import sinon = require("sinon");
+import jquery = require("jquery");
+
+
+export interface JQueryMock extends sinon.SinonStub {
+    get: sinon.SinonStub;
+    post: sinon.SinonStub;
+    ajax: sinon.SinonStub;
+    notify: sinon.SinonStub;
+}
+
+export interface JQueryElementsMock {
+    ready: sinon.SinonStub;
+    show: sinon.SinonStub;
+    hide: sinon.SinonStub;
+    html: sinon.SinonStub;
+    addClass: sinon.SinonStub;
+    removeClass: sinon.SinonStub;
+    fadeIn: sinon.SinonStub;
+    fadeOut: sinon.SinonStub;
+    on: sinon.SinonStub;
+}
+
+export interface JQueryDeferredMock {
+    done: sinon.SinonStub;
+    fail: sinon.SinonStub;
+}
+
+export function JQueryMock(): { jquery: JQueryMock, element: JQueryElementsMock } {
+    const jquery = sinon.stub() as any;
+    const jqueryInstance: JQueryElementsMock = {
+        ready: sinon.stub(),
+        show: sinon.stub(),
+        hide: sinon.stub(),
+        html: sinon.stub(),
+        addClass: sinon.stub(),
+        removeClass: sinon.stub(),
+        fadeIn: sinon.stub(),
+        fadeOut: sinon.stub(),
+        on: sinon.stub()
+    };
+    jquery.ajax = sinon.stub();
+    jquery.get = sinon.stub();
+    jquery.post = sinon.stub();
+    jquery.notify = sinon.stub();
+    jquery.returns(jqueryInstance);
+    return {
+        jquery: jquery,
+        element: jqueryInstance
+    };
+}
+
+export function JQueryDeferredMock(): JQueryDeferredMock {
+    return {
+        done: sinon.stub(),
+        fail: sinon.stub()
+    };
+}
diff --git a/themes/triangles/client/test/mocks/u2f-api.ts b/themes/triangles/client/test/mocks/u2f-api.ts
new file mode 100644
index 00000000..d123f6a9
--- /dev/null
+++ b/themes/triangles/client/test/mocks/u2f-api.ts
@@ -0,0 +1,14 @@
+
+import sinon = require("sinon");
+
+export interface U2FApiMock {
+    sign: sinon.SinonStub;
+    register: sinon.SinonStub;
+}
+
+export function U2FApiMock(): U2FApiMock {
+    return {
+        sign: sinon.stub(),
+        register: sinon.stub()
+    };
+}
\ No newline at end of file
diff --git a/themes/triangles/client/test/secondfactor/TOTPValidator.test.ts b/themes/triangles/client/test/secondfactor/TOTPValidator.test.ts
new file mode 100644
index 00000000..5dd6f15c
--- /dev/null
+++ b/themes/triangles/client/test/secondfactor/TOTPValidator.test.ts
@@ -0,0 +1,37 @@
+
+import TOTPValidator = require("../../src/lib/secondfactor/TOTPValidator");
+import JQueryMock = require("../mocks/jquery");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+
+describe("test TOTPValidator", function () {
+  it("should initiate an identity check successfully", () => {
+    const postPromise = JQueryMock.JQueryDeferredMock();
+    postPromise.done.yields({ redirect: "https://home.test.url" });
+    postPromise.done.returns(postPromise);
+
+    const jqueryMock = JQueryMock.JQueryMock();
+    jqueryMock.jquery.ajax.returns(postPromise);
+
+    return TOTPValidator.validate("totp_token", jqueryMock.jquery as any);
+  });
+
+  it("should fail validating TOTP token", () => {
+    const errorMessage = "Error while validating TOTP token";
+
+    const postPromise = JQueryMock.JQueryDeferredMock();
+    postPromise.fail.yields(undefined, errorMessage);
+    postPromise.done.returns(postPromise);
+
+    const jqueryMock = JQueryMock.JQueryMock();
+    jqueryMock.jquery.ajax.returns(postPromise);
+
+    return TOTPValidator.validate("totp_token", jqueryMock.jquery as any)
+      .then(function () {
+        return BluebirdPromise.reject(new Error("Registration successfully finished while it should have not."));
+      }, function (err: Error) {
+        Assert.equal(errorMessage, err.message);
+        return BluebirdPromise.resolve();
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/client/test/totp-register/totp-register.test.ts b/themes/triangles/client/test/totp-register/totp-register.test.ts
new file mode 100644
index 00000000..86fc455a
--- /dev/null
+++ b/themes/triangles/client/test/totp-register/totp-register.test.ts
@@ -0,0 +1,31 @@
+
+import sinon = require("sinon");
+import assert = require("assert");
+
+import UISelector = require("../../src/lib/totp-register/ui-selector");
+import TOTPRegister = require("../../src/lib/totp-register/totp-register");
+
+describe("test totp-register", function() {
+    let jqueryMock: any;
+    let windowMock: any;
+    before(function() {
+        jqueryMock = sinon.stub();
+        windowMock = {
+            QRCode: sinon.spy()
+        };
+    });
+
+    it("should create qrcode in page", function() {
+        const mock = {
+            text: sinon.stub(),
+            empty: sinon.stub(),
+            get: sinon.stub()
+        };
+        jqueryMock.withArgs(UISelector.QRCODE_ID_SELECTOR).returns(mock);
+
+        TOTPRegister.default(windowMock, jqueryMock);
+
+        assert(mock.text.calledOnce);
+        assert(mock.empty.calledOnce);
+    });
+});
\ No newline at end of file
diff --git a/themes/triangles/client/tsconfig.json b/themes/triangles/client/tsconfig.json
new file mode 100644
index 00000000..0bb4d62f
--- /dev/null
+++ b/themes/triangles/client/tsconfig.json
@@ -0,0 +1,24 @@
+{
+    "compilerOptions": {
+        "module": "commonjs",
+        "target": "es6",
+        "moduleResolution": "node",
+        "noImplicitAny": true,
+        "sourceMap": true,
+        "removeComments": true,
+        "outDir": "../dist",
+        "baseUrl": ".",
+        "paths": {
+            "*": [
+                "./types/*",
+                "../shared/types/*"
+            ]
+        }
+    },
+    "include": [
+        "src/**/*"
+    ],
+    "exclude": [
+        "test/**/*"
+    ]
+}
diff --git a/themes/triangles/client/tslint.json b/themes/triangles/client/tslint.json
new file mode 100644
index 00000000..c2c1b750
--- /dev/null
+++ b/themes/triangles/client/tslint.json
@@ -0,0 +1,60 @@
+{
+    "rules": {
+        "class-name": true,
+        "comment-format": [
+            true,
+            "check-space"
+        ],
+        "indent": [
+            true,
+            "spaces"
+        ],
+        "one-line": [
+            true,
+            "check-open-brace",
+            "check-whitespace"
+        ],
+        "no-var-keyword": true,
+        "quotemark": [
+            true,
+            "double",
+            "avoid-escape"
+        ],
+        "semicolon": [
+            true,
+            "always",
+            "ignore-bound-class-methods"
+        ],
+        "whitespace": [
+            true,
+            "check-branch",
+            "check-decl",
+            "check-operator",
+            "check-module",
+            "check-separator",
+            "check-type"
+        ],
+        "typedef-whitespace": [
+            true,
+            {
+                "call-signature": "nospace",
+                "index-signature": "nospace",
+                "parameter": "nospace",
+                "property-declaration": "nospace",
+                "variable-declaration": "nospace"
+            },
+            {
+                "call-signature": "onespace",
+                "index-signature": "onespace",
+                "parameter": "onespace",
+                "property-declaration": "onespace",
+                "variable-declaration": "onespace"
+            }
+        ],
+        "no-internal-module": true,
+        "no-trailing-whitespace": true,
+        "no-null-keyword": true,
+        "prefer-const": true,
+        "jsdoc-format": true
+    }
+}
diff --git a/themes/triangles/server/.directory b/themes/triangles/server/.directory
new file mode 100644
index 00000000..b7754766
--- /dev/null
+++ b/themes/triangles/server/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,58,20
+Version=3
+ViewMode=1
diff --git a/themes/triangles/server/src/index.ts b/themes/triangles/server/src/index.ts
new file mode 100755
index 00000000..fcbf4d02
--- /dev/null
+++ b/themes/triangles/server/src/index.ts
@@ -0,0 +1,28 @@
+#! /usr/bin/env node
+
+import Server from "./lib/Server";
+import { GlobalDependencies } from "../types/Dependencies";
+import YAML = require("yamljs");
+
+const configurationFilepath = process.argv[2];
+if (!configurationFilepath) {
+  console.log("No config file has been provided.");
+  console.log("Usage: authelia <config>");
+  process.exit(0);
+}
+
+const yamlContent = YAML.load(configurationFilepath);
+
+const deps: GlobalDependencies = {
+  u2f: require("u2f"),
+  ldapjs: require("ldapjs"),
+  session: require("express-session"),
+  winston: require("winston"),
+  speakeasy: require("speakeasy"),
+  nedb: require("nedb"),
+  ConnectRedis: require("connect-redis"),
+  Redis: require("redis")
+};
+
+const server = new Server(deps);
+server.start(yamlContent, deps);
diff --git a/themes/triangles/server/src/lib/.directory b/themes/triangles/server/src/lib/.directory
new file mode 100644
index 00000000..006b379a
--- /dev/null
+++ b/themes/triangles/server/src/lib/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,59,13
+Version=3
+ViewMode=1
diff --git a/themes/triangles/server/src/lib/AuthenticationSessionHandler.ts b/themes/triangles/server/src/lib/AuthenticationSessionHandler.ts
new file mode 100644
index 00000000..57361bf8
--- /dev/null
+++ b/themes/triangles/server/src/lib/AuthenticationSessionHandler.ts
@@ -0,0 +1,45 @@
+
+
+import express = require("express");
+import U2f = require("u2f");
+import BluebirdPromise = require("bluebird");
+import { AuthenticationSession } from "../../types/AuthenticationSession";
+import { IRequestLogger } from "./logging/IRequestLogger";
+import { Level } from "./authentication/Level";
+
+const INITIAL_AUTHENTICATION_SESSION: AuthenticationSession = {
+  keep_me_logged_in: false,
+  authentication_level: Level.NOT_AUTHENTICATED,
+  last_activity_datetime: undefined,
+  userid: undefined,
+  email: undefined,
+  groups: [],
+  register_request: undefined,
+  sign_request: undefined,
+  identity_check: undefined,
+  redirect: undefined
+};
+
+export class AuthenticationSessionHandler {
+  static reset(req: express.Request): void {
+    req.session.auth = Object.assign({}, INITIAL_AUTHENTICATION_SESSION, {});
+
+    // Initialize last activity with current time
+    req.session.auth.last_activity_datetime = new Date().getTime();
+  }
+
+  static get(req: express.Request, logger: IRequestLogger): AuthenticationSession {
+    if (!req.session) {
+      const errorMsg = "Something is wrong with session cookies. Please check Redis is running and Authelia can connect to it.";
+      logger.error(req, errorMsg);
+      throw new Error(errorMsg);
+    }
+
+    if (!req.session.auth) {
+      logger.debug(req, "Authentication session %s was undefined. Resetting.", req.sessionID);
+      AuthenticationSessionHandler.reset(req);
+    }
+
+    return req.session.auth;
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/ErrorReplies.ts b/themes/triangles/server/src/lib/ErrorReplies.ts
new file mode 100644
index 00000000..f1c5f4fd
--- /dev/null
+++ b/themes/triangles/server/src/lib/ErrorReplies.ts
@@ -0,0 +1,49 @@
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import { IRequestLogger } from "./logging/IRequestLogger";
+
+function replyWithError(req: express.Request, res: express.Response,
+  code: number, logger: IRequestLogger, body?: Object): (err: Error) => void {
+  return function (err: Error): void {
+    if (req.originalUrl.startsWith("/api/") || code == 200) {
+      logger.error(req, "Reply with error %d: %s", code, err.message);
+      logger.debug(req, "%s", err.stack);
+      res.status(code);
+      res.send(body);
+    }
+    else {
+      logger.error(req, "Redirect to error %d: %s", code, err.message);
+      logger.debug(req, "%s", err.stack);
+      res.redirect("/error/" + code);
+    }
+  };
+}
+
+export function redirectTo(redirectUrl: string, req: express.Request,
+  res: express.Response, logger: IRequestLogger) {
+  return function(err: Error) {
+    logger.error(req, "Error: %s", err.message);
+    logger.debug(req, "Redirecting to %s", redirectUrl);
+    res.redirect(redirectUrl);
+  };
+}
+
+export function replyWithError400(req: express.Request,
+  res: express.Response, logger: IRequestLogger) {
+  return replyWithError(req, res, 400, logger);
+}
+
+export function replyWithError401(req: express.Request,
+  res: express.Response, logger: IRequestLogger) {
+  return replyWithError(req, res, 401, logger);
+}
+
+export function replyWithError403(req: express.Request,
+  res: express.Response, logger: IRequestLogger) {
+  return replyWithError(req, res, 403, logger);
+}
+
+export function replyWithError200(req: express.Request,
+  res: express.Response, logger: IRequestLogger, message: string) {
+  return replyWithError(req, res, 200, logger, { error: message });
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/Exceptions.ts b/themes/triangles/server/src/lib/Exceptions.ts
new file mode 100644
index 00000000..83fa4eb6
--- /dev/null
+++ b/themes/triangles/server/src/lib/Exceptions.ts
@@ -0,0 +1,88 @@
+
+export class LdapSearchError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "LdapSearchError";
+    (<any>Object).setPrototypeOf(this, LdapSearchError.prototype);
+  }
+}
+
+export class LdapBindError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "LdapBindError";
+    (<any>Object).setPrototypeOf(this, LdapBindError.prototype);
+  }
+}
+
+export class LdapError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "LdapError";
+    (<any>Object).setPrototypeOf(this, LdapError.prototype);
+  }
+}
+
+export class IdentityError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "IdentityError";
+    (<any>Object).setPrototypeOf(this, IdentityError.prototype);
+  }
+}
+
+export class AccessDeniedError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "AccessDeniedError";
+    (<any>Object).setPrototypeOf(this, AccessDeniedError.prototype);
+  }
+}
+
+export class AuthenticationRegulationError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "AuthenticationRegulationError";
+    (<any>Object).setPrototypeOf(this, AuthenticationRegulationError.prototype);
+  }
+}
+
+export class InvalidTOTPError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "InvalidTOTPError";
+    (<any>Object).setPrototypeOf(this, InvalidTOTPError.prototype);
+  }
+}
+
+export class NotAuthenticatedError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "NotAuthenticatedError";
+    (<any>Object).setPrototypeOf(this, NotAuthenticatedError.prototype);
+  }
+}
+
+export class NotAuthorizedError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "NotAuthanticatedError";
+    (<any>Object).setPrototypeOf(this, NotAuthorizedError.prototype);
+  }
+}
+
+export class FirstFactorValidationError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "FirstFactorValidationError";
+    (<any>Object).setPrototypeOf(this, FirstFactorValidationError.prototype);
+  }
+}
+
+export class SecondFactorValidationError extends Error {
+  constructor(message?: string) {
+    super(message);
+    this.name = "SecondFactorValidationError";
+    (<any>Object).setPrototypeOf(this, FirstFactorValidationError.prototype);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/FirstFactorValidator.ts b/themes/triangles/server/src/lib/FirstFactorValidator.ts
new file mode 100644
index 00000000..23106000
--- /dev/null
+++ b/themes/triangles/server/src/lib/FirstFactorValidator.ts
@@ -0,0 +1,20 @@
+
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import objectPath = require("object-path");
+import Exceptions = require("./Exceptions");
+import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
+import { IRequestLogger } from "./logging/IRequestLogger";
+import { Level } from "./authentication/Level";
+
+export function validate(req: express.Request, logger: IRequestLogger): BluebirdPromise<void> {
+  return new BluebirdPromise(function (resolve, reject) {
+    const authSession = AuthenticationSessionHandler.get(req, logger);
+
+    if (!authSession.userid || authSession.authentication_level < Level.ONE_FACTOR)
+      return reject(new Exceptions.FirstFactorValidationError(
+        "First factor has not been validated yet."));
+
+    resolve();
+  });
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/IdentityCheckMiddleware.spec.ts b/themes/triangles/server/src/lib/IdentityCheckMiddleware.spec.ts
new file mode 100644
index 00000000..842ed6bc
--- /dev/null
+++ b/themes/triangles/server/src/lib/IdentityCheckMiddleware.spec.ts
@@ -0,0 +1,176 @@
+
+import sinon = require("sinon");
+import IdentityValidator = require("./IdentityCheckMiddleware");
+import { AuthenticationSessionHandler }
+  from "./AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../types/AuthenticationSession";
+import { UserDataStore } from "./storage/UserDataStore";
+import exceptions = require("./Exceptions");
+import { ServerVariables } from "./ServerVariables";
+import Assert = require("assert");
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import ExpressMock = require("./stubs/express.spec");
+import NotifierMock = require("./notifiers/NotifierStub.spec");
+import { IdentityValidableStub } from "./IdentityValidableStub.spec";
+import { RequestLoggerStub } from "./logging/RequestLoggerStub.spec";
+import { ServerVariablesMock, ServerVariablesMockBuilder }
+  from "./ServerVariablesMockBuilder.spec";
+import { PRE_VALIDATION_TEMPLATE }
+  from "./IdentityCheckPreValidationTemplate";
+
+
+describe("IdentityCheckMiddleware", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let app: express.Application;
+  let app_get: sinon.SinonStub;
+  let app_post: sinon.SinonStub;
+  let identityValidable: IdentityValidableStub;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req = ExpressMock.RequestMock();
+    res = ExpressMock.ResponseMock();
+
+    req.headers = {};
+    req.originalUrl = "/non-api/xxx";
+    req.session = {};
+
+    req.query = {};
+    req.app = {};
+
+    identityValidable = new IdentityValidableStub();
+
+    mocks.notifier.notifyStub.returns(BluebirdPromise.resolve());
+    mocks.userDataStore.produceIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve());
+    mocks.userDataStore.consumeIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve({ userId: "user" }));
+
+    app = express();
+    app_get = sinon.stub(app, "get");
+    app_post = sinon.stub(app, "post");
+  });
+
+  afterEach(function () {
+    app_get.restore();
+    app_post.restore();
+  });
+
+  describe("test start GET", function () {
+    it("should redirect to error 401 if pre validation initialization \
+throws a first factor error", function () {
+        identityValidable.preValidationInitStub.returns(BluebirdPromise.reject(
+          new exceptions.FirstFactorValidationError(
+            "Error during prevalidation")));
+        const callback = IdentityValidator.get_start_validation(
+          identityValidable, "/endpoint", vars);
+
+        return callback(req as any, res as any, undefined)
+          .then(() => {
+            Assert(res.redirect.calledWith("/error/401"));
+          });
+      });
+
+    // In that case we answer with 200 to avoid user enumeration.
+    it("should send 200 if email is missing in provided identity", function () {
+      const identity = { userid: "abc" };
+
+      identityValidable.preValidationInitStub
+        .returns(BluebirdPromise.resolve(identity));
+      const callback = IdentityValidator
+        .get_start_validation(identityValidable, "/endpoint", vars);
+
+      return callback(req as any, res as any, undefined)
+        .then(function () {
+          Assert(identityValidable.preValidationResponseStub.called);
+        });
+    });
+
+    // In that case we answer with 200 to avoid user enumeration.
+    it("should send 200 if userid is missing in provided identity",
+      function () {
+        const endpoint = "/protected";
+        const identity = { email: "abc@example.com" };
+
+        identityValidable.preValidationInitStub
+          .returns(BluebirdPromise.resolve(identity));
+        const callback = IdentityValidator
+          .get_start_validation(identityValidable, "/endpoint", vars);
+
+        return callback(req as any, res as any, undefined)
+          .then(function () {
+            Assert(identityValidable.preValidationResponseStub.called);
+          });
+      });
+
+    it("should issue a token, send an email and return 204", function () {
+      const endpoint = "/protected";
+      const identity = { userid: "user", email: "abc@example.com" };
+      req.get = sinon.stub().withArgs("Host").returns("localhost");
+
+      identityValidable.preValidationInitStub
+        .returns(BluebirdPromise.resolve(identity));
+      const callback = IdentityValidator
+        .get_start_validation(identityValidable, "/finish_endpoint", vars);
+
+      return callback(req as any, res as any, undefined)
+        .then(function () {
+          Assert(mocks.notifier.notifyStub.calledOnce);
+          Assert(mocks.userDataStore.produceIdentityValidationTokenStub
+            .calledOnce);
+          Assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub
+            .getCall(0).args[0], "user");
+          Assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub
+            .getCall(0).args[3], 240000);
+        });
+    });
+  });
+
+
+
+  describe("test finish GET", function () {
+    it("should send 401 if no identity_token is provided", () => {
+      const callback = IdentityValidator
+        .get_finish_validation(identityValidable, vars);
+
+      return callback(req as any, res as any, undefined)
+        .then(function () {
+          Assert(res.redirect.calledWith("/error/401"));
+        });
+    });
+
+    it("should call postValidation if identity_token is provided and still \
+valid", function () {
+        req.query.identity_token = "token";
+
+        const callback = IdentityValidator
+          .get_finish_validation(identityValidable, vars);
+        return callback(req as any, res as any, undefined);
+      });
+
+    it("should return 401 if identity_token is provided but invalid",
+      function () {
+        req.query.identity_token = "token";
+
+        identityValidable.postValidationInitStub
+          .returns(BluebirdPromise.resolve());
+        mocks.userDataStore.consumeIdentityValidationTokenStub.reset();
+        mocks.userDataStore.consumeIdentityValidationTokenStub
+          .returns(BluebirdPromise.reject(new Error("Invalid token")));
+
+        const callback = IdentityValidator
+          .get_finish_validation(identityValidable, vars);
+        return callback(req as any, res as any, undefined)
+          .then(() => {
+            Assert(res.redirect.calledWith("/error/401"));
+          });
+      });
+  });
+});
diff --git a/themes/triangles/server/src/lib/IdentityCheckMiddleware.ts b/themes/triangles/server/src/lib/IdentityCheckMiddleware.ts
new file mode 100644
index 00000000..e72ea4db
--- /dev/null
+++ b/themes/triangles/server/src/lib/IdentityCheckMiddleware.ts
@@ -0,0 +1,138 @@
+import objectPath = require("object-path");
+import randomstring = require("randomstring");
+import BluebirdPromise = require("bluebird");
+import util = require("util");
+import Exceptions = require("./Exceptions");
+import fs = require("fs");
+import ejs = require("ejs");
+import { IUserDataStore } from "./storage/IUserDataStore";
+import Express = require("express");
+import ErrorReplies = require("./ErrorReplies");
+import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../types/AuthenticationSession";
+import { ServerVariables } from "./ServerVariables";
+import { IdentityValidable } from "./IdentityValidable";
+
+import Identity = require("../../types/Identity");
+import { IdentityValidationDocument }
+  from "./storage/IdentityValidationDocument";
+
+const filePath = __dirname + "/../resources/email-template.ejs";
+const email_template = fs.readFileSync(filePath, "utf8");
+
+function createAndSaveToken(userid: string, challenge: string,
+  userDataStore: IUserDataStore): BluebirdPromise<string> {
+
+  const five_minutes = 4 * 60 * 1000;
+  const token = randomstring.generate({ length: 64 });
+  const that = this;
+
+  return userDataStore.produceIdentityValidationToken(userid, token, challenge,
+    five_minutes)
+    .then(function () {
+      return BluebirdPromise.resolve(token);
+    });
+}
+
+function consumeToken(token: string, challenge: string,
+  userDataStore: IUserDataStore)
+  : BluebirdPromise<IdentityValidationDocument> {
+  return userDataStore.consumeIdentityValidationToken(token, challenge);
+}
+
+export function register(app: Express.Application,
+  pre_validation_endpoint: string,
+  post_validation_endpoint: string,
+  handler: IdentityValidable,
+  vars: ServerVariables) {
+
+  app.get(pre_validation_endpoint,
+    get_start_validation(handler, post_validation_endpoint, vars));
+  app.get(post_validation_endpoint,
+    get_finish_validation(handler, vars));
+}
+
+function checkIdentityToken(req: Express.Request, identityToken: string)
+  : BluebirdPromise<void> {
+  if (!identityToken)
+    return BluebirdPromise.reject(
+      new Exceptions.AccessDeniedError("No identity token provided"));
+  return BluebirdPromise.resolve();
+}
+
+export function get_finish_validation(handler: IdentityValidable,
+  vars: ServerVariables)
+  : Express.RequestHandler {
+
+  return function (req: Express.Request, res: Express.Response)
+    : BluebirdPromise<void> {
+
+    let authSession: AuthenticationSession;
+    const identityToken = objectPath.get<Express.Request, string>(
+      req, "query.identity_token");
+    vars.logger.debug(req, "Identity token provided is %s", identityToken);
+
+    return checkIdentityToken(req, identityToken)
+      .then(() => {
+        authSession = AuthenticationSessionHandler.get(req, vars.logger);
+        return handler.postValidationInit(req);
+      })
+      .then(() => {
+        return consumeToken(identityToken, handler.challenge(),
+          vars.userDataStore);
+      })
+      .then((doc: IdentityValidationDocument) => {
+        authSession.identity_check = {
+          challenge: handler.challenge(),
+          userid: doc.userId
+        };
+        handler.postValidationResponse(req, res);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError401(req, res, vars.logger));
+  };
+}
+
+export function get_start_validation(handler: IdentityValidable,
+  postValidationEndpoint: string,
+  vars: ServerVariables)
+  : Express.RequestHandler {
+  return function (req: Express.Request, res: Express.Response)
+    : BluebirdPromise<void> {
+    let identity: Identity.Identity;
+
+    return handler.preValidationInit(req)
+      .then((id: Identity.Identity) => {
+        identity = id;
+        const email = identity.email;
+        const userid = identity.userid;
+        vars.logger.info(req, "Start identity validation of user \"%s\"",
+          userid);
+
+        if (!(email && userid))
+          return BluebirdPromise.reject(new Exceptions.IdentityError(
+            "Missing user id or email address"));
+
+        return createAndSaveToken(userid, handler.challenge(),
+          vars.userDataStore);
+      })
+      .then((token) => {
+        const host = req.get("Host");
+        const link_url = util.format("https://%s%s?identity_token=%s", host,
+          postValidationEndpoint, token);
+        vars.logger.info(req, "Notification sent to user \"%s\"",
+          identity.userid);
+        return vars.notifier.notify(identity.email, handler.mailSubject(),
+          link_url);
+      })
+      .then(() => {
+        handler.preValidationResponse(req, res);
+        return BluebirdPromise.resolve();
+      })
+      .catch(Exceptions.IdentityError, (err: Error) => {
+        handler.preValidationResponse(req, res);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError401(req, res, vars.logger));
+  };
+}
diff --git a/themes/triangles/server/src/lib/IdentityCheckPreValidationTemplate.ts b/themes/triangles/server/src/lib/IdentityCheckPreValidationTemplate.ts
new file mode 100644
index 00000000..0161ce40
--- /dev/null
+++ b/themes/triangles/server/src/lib/IdentityCheckPreValidationTemplate.ts
@@ -0,0 +1,3 @@
+
+
+export const PRE_VALIDATION_TEMPLATE = "need-identity-validation";
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/IdentityValidable.ts b/themes/triangles/server/src/lib/IdentityValidable.ts
new file mode 100644
index 00000000..075580c9
--- /dev/null
+++ b/themes/triangles/server/src/lib/IdentityValidable.ts
@@ -0,0 +1,19 @@
+import Bluebird = require("bluebird");
+import Identity = require("../../types/Identity");
+
+// IdentityValidator allows user to go through a identity validation process
+// in two steps:
+// - Request an operation to be performed (password reset, registration).
+// - Confirm operation with email.
+
+export interface IdentityValidable {
+  challenge(): string;
+  preValidationInit(req: Express.Request): Bluebird<Identity.Identity>;
+  postValidationInit(req: Express.Request): Bluebird<void>;
+
+  // Serves a page after identity check request
+  preValidationResponse(req: Express.Request, res: Express.Response): void;
+  // Serves the page if identity validated
+  postValidationResponse(req: Express.Request, res: Express.Response): void;
+  mailSubject(): string;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/IdentityValidableStub.spec.ts b/themes/triangles/server/src/lib/IdentityValidableStub.spec.ts
new file mode 100644
index 00000000..20a97714
--- /dev/null
+++ b/themes/triangles/server/src/lib/IdentityValidableStub.spec.ts
@@ -0,0 +1,52 @@
+
+import Sinon = require("sinon");
+import { IdentityValidable } from "./IdentityValidable";
+import express = require("express");
+import Bluebird = require("bluebird");
+import { Identity } from "../../types/Identity";
+
+
+export class IdentityValidableStub implements IdentityValidable {
+    challengeStub: Sinon.SinonStub;
+    preValidationInitStub: Sinon.SinonStub;
+    postValidationInitStub: Sinon.SinonStub;
+    preValidationResponseStub: Sinon.SinonStub;
+    postValidationResponseStub: Sinon.SinonStub;
+    mailSubjectStub: Sinon.SinonStub;
+
+    constructor() {
+        this.challengeStub = Sinon.stub();
+
+        this.preValidationInitStub = Sinon.stub();
+        this.postValidationInitStub = Sinon.stub();
+
+        this.preValidationResponseStub = Sinon.stub();
+        this.postValidationResponseStub = Sinon.stub();
+
+        this.mailSubjectStub = Sinon.stub();
+    }
+
+    challenge(): string {
+        return this.challengeStub();
+    }
+
+    preValidationInit(req: Express.Request): Bluebird<Identity> {
+        return this.preValidationInitStub(req);
+    }
+
+    postValidationInit(req: Express.Request): Bluebird<void> {
+        return this.postValidationInitStub(req);
+    }
+
+    preValidationResponse(req: Express.Request, res: Express.Response): void {
+        return this.preValidationResponseStub(req, res);
+    }
+
+    postValidationResponse(req: Express.Request, res: Express.Response): void {
+        return this.postValidationResponseStub(req, res);
+    }
+
+    mailSubject(): string {
+        return this.mailSubjectStub();
+    }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/Server.spec.ts b/themes/triangles/server/src/lib/Server.spec.ts
new file mode 100644
index 00000000..36516325
--- /dev/null
+++ b/themes/triangles/server/src/lib/Server.spec.ts
@@ -0,0 +1,81 @@
+
+import Assert = require("assert");
+import Sinon = require("sinon");
+import nedb = require("nedb");
+import express = require("express");
+import winston = require("winston");
+import speakeasy = require("speakeasy");
+import u2f = require("u2f");
+import session = require("express-session");
+import { Configuration } from "./configuration/schema/Configuration";
+import { GlobalDependencies } from "../../types/Dependencies";
+import Server from "./Server";
+import { LdapjsMock, LdapjsClientMock } from "./stubs/ldapjs.spec";
+
+
+describe("Server", function () {
+  let deps: GlobalDependencies;
+  let sessionMock: Sinon.SinonSpy;
+  let ldapjsMock: LdapjsMock;
+
+  before(function () {
+    sessionMock = Sinon.spy(session);
+    ldapjsMock = new LdapjsMock();
+
+    deps = {
+      speakeasy: speakeasy,
+      u2f: u2f,
+      nedb: nedb,
+      winston: winston,
+      ldapjs: ldapjsMock as any,
+      session: sessionMock as any,
+      ConnectRedis: Sinon.spy(),
+      Redis: Sinon.spy() as any
+    };
+  });
+
+
+  it("should set cookie scope to domain set in the config", function () {
+    const config: Configuration = {
+      port: 8081,
+      session: {
+        domain: "example.com",
+        secret: "secret"
+      },
+      authentication_backend: {
+        ldap: {
+          url: "http://ldap",
+          user: "user",
+          password: "password",
+          base_dn: "dc=example,dc=com"
+        },
+      },
+      notifier: {
+        email: {
+          username: "user@example.com",
+          password: "password",
+          sender: "test@authelia.com",
+          service: "gmail"
+        }
+      },
+      regulation: {
+        max_retries: 3,
+        ban_time: 5 * 60,
+        find_time: 5 * 60
+      },
+      storage: {
+        local: {
+          in_memory: true
+        }
+      }
+    };
+
+    const server = new Server(deps);
+    server.start(config, deps)
+      .then(function () {
+        Assert(sessionMock.calledOnce);
+        Assert.equal(sessionMock.getCall(0).args[0].cookie.domain, "example.com");
+        server.stop();
+      });
+  });
+});
diff --git a/themes/triangles/server/src/lib/Server.ts b/themes/triangles/server/src/lib/Server.ts
new file mode 100644
index 00000000..4090f629
--- /dev/null
+++ b/themes/triangles/server/src/lib/Server.ts
@@ -0,0 +1,93 @@
+import BluebirdPromise = require("bluebird");
+import ObjectPath = require("object-path");
+
+import { Configuration } from "./configuration/schema/Configuration";
+import { GlobalDependencies } from "../../types/Dependencies";
+import { UserDataStore } from "./storage/UserDataStore";
+import { ConfigurationParser } from "./configuration/ConfigurationParser";
+import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder";
+import { GlobalLogger } from "./logging/GlobalLogger";
+import { RequestLogger } from "./logging/RequestLogger";
+import { ServerVariables } from "./ServerVariables";
+import { ServerVariablesInitializer } from "./ServerVariablesInitializer";
+import { Configurator } from "./web_server/Configurator";
+
+import * as Express from "express";
+import * as Path from "path";
+import * as http from "http";
+
+function clone(obj: any) {
+  return JSON.parse(JSON.stringify(obj));
+}
+
+export default class Server {
+  private httpServer: http.Server;
+  private globalLogger: GlobalLogger;
+  private requestLogger: RequestLogger;
+
+  constructor(deps: GlobalDependencies) {
+    this.globalLogger = new GlobalLogger(deps.winston);
+    this.requestLogger = new RequestLogger(deps.winston);
+  }
+
+  private displayConfigurations(configuration: Configuration) {
+    const displayableConfiguration: Configuration = clone(configuration);
+    const STARS = "*****";
+
+    if (displayableConfiguration.authentication_backend.ldap) {
+      displayableConfiguration.authentication_backend.ldap.password = STARS;
+    }
+
+    displayableConfiguration.session.secret = STARS;
+    if (displayableConfiguration.notifier && displayableConfiguration.notifier.email)
+      displayableConfiguration.notifier.email.password = STARS;
+    if (displayableConfiguration.notifier && displayableConfiguration.notifier.smtp)
+      displayableConfiguration.notifier.smtp.password = STARS;
+
+    this.globalLogger.debug("User configuration is %s",
+      JSON.stringify(displayableConfiguration, undefined, 2));
+  }
+
+  private setup(config: Configuration, app: Express.Application, deps: GlobalDependencies): BluebirdPromise<void> {
+    const that = this;
+    return ServerVariablesInitializer.initialize(
+      config, this.globalLogger, this.requestLogger, deps)
+      .then(function (vars: ServerVariables) {
+        Configurator.configure(config, app, vars, deps);
+        return BluebirdPromise.resolve();
+      });
+  }
+
+  private startServer(app: Express.Application, port: number) {
+    const that = this;
+    that.globalLogger.info("Starting Authelia...");
+    return new BluebirdPromise<void>((resolve, reject) => {
+      this.httpServer = app.listen(port, function (err: string) {
+        that.globalLogger.info("Listening on port %d...", port);
+        resolve();
+      });
+    });
+  }
+
+  start(configuration: Configuration, deps: GlobalDependencies)
+    : BluebirdPromise<void> {
+    const that = this;
+    const app = Express();
+
+    const appConfiguration = ConfigurationParser.parse(configuration);
+
+    // by default the level of logs is info
+    deps.winston.level = appConfiguration.logs_level;
+    this.displayConfigurations(appConfiguration);
+
+    return this.setup(appConfiguration, app, deps)
+      .then(function () {
+        return that.startServer(app, appConfiguration.port);
+      });
+  }
+
+  stop() {
+    this.httpServer.close();
+  }
+}
+
diff --git a/themes/triangles/server/src/lib/ServerVariables.ts b/themes/triangles/server/src/lib/ServerVariables.ts
new file mode 100644
index 00000000..cd3dd6dc
--- /dev/null
+++ b/themes/triangles/server/src/lib/ServerVariables.ts
@@ -0,0 +1,21 @@
+import { IRequestLogger } from "./logging/IRequestLogger";
+import { ITotpHandler } from "./authentication/totp/ITotpHandler";
+import { IU2fHandler } from "./authentication/u2f/IU2fHandler";
+import { IUserDataStore } from "./storage/IUserDataStore";
+import { INotifier } from "./notifiers/INotifier";
+import { IRegulator } from "./regulation/IRegulator";
+import { Configuration } from "./configuration/schema/Configuration";
+import { IAuthorizer } from "./authorization/IAuthorizer";
+import { IUsersDatabase } from "./authentication/backends/IUsersDatabase";
+
+export interface ServerVariables {
+  logger: IRequestLogger;
+  usersDatabase: IUsersDatabase;
+  totpHandler: ITotpHandler;
+  u2f: IU2fHandler;
+  userDataStore: IUserDataStore;
+  notifier: INotifier;
+  regulator: IRegulator;
+  config: Configuration;
+  authorizer: IAuthorizer;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/ServerVariablesInitializer.ts b/themes/triangles/server/src/lib/ServerVariablesInitializer.ts
new file mode 100644
index 00000000..df79238c
--- /dev/null
+++ b/themes/triangles/server/src/lib/ServerVariablesInitializer.ts
@@ -0,0 +1,116 @@
+
+import winston = require("winston");
+import BluebirdPromise = require("bluebird");
+import U2F = require("u2f");
+import Nodemailer = require("nodemailer");
+
+import { IRequestLogger } from "./logging/IRequestLogger";
+import { RequestLogger } from "./logging/RequestLogger";
+
+import { TotpHandler } from "./authentication/totp/TotpHandler";
+import { ITotpHandler } from "./authentication/totp/ITotpHandler";
+import { NotifierFactory } from "./notifiers/NotifierFactory";
+import { MailSenderBuilder } from "./notifiers/MailSenderBuilder";
+import { LdapUsersDatabase } from "./authentication/backends/ldap/LdapUsersDatabase";
+import { ConnectorFactory } from "./authentication/backends/ldap/connector/ConnectorFactory";
+
+import { IUserDataStore } from "./storage/IUserDataStore";
+import { UserDataStore } from "./storage/UserDataStore";
+import { INotifier } from "./notifiers/INotifier";
+import { Regulator } from "./regulation/Regulator";
+import { IRegulator } from "./regulation/IRegulator";
+import Configuration = require("./configuration/schema/Configuration");
+import { CollectionFactoryFactory } from "./storage/CollectionFactoryFactory";
+import { ICollectionFactory } from "./storage/ICollectionFactory";
+import { MongoCollectionFactory } from "./storage/mongo/MongoCollectionFactory";
+import { IMongoClient } from "./connectors/mongo/IMongoClient";
+
+import { GlobalDependencies } from "../../types/Dependencies";
+import { ServerVariables } from "./ServerVariables";
+import { MongoClient } from "./connectors/mongo/MongoClient";
+import { IGlobalLogger } from "./logging/IGlobalLogger";
+import { SessionFactory } from "./authentication/backends/ldap/SessionFactory";
+import { IUsersDatabase } from "./authentication/backends/IUsersDatabase";
+import { FileUsersDatabase } from "./authentication/backends/file/FileUsersDatabase";
+import { Authorizer } from "./authorization/Authorizer";
+
+class UserDataStoreFactory {
+  static create(config: Configuration.Configuration, globalLogger: IGlobalLogger): BluebirdPromise<UserDataStore> {
+    if (config.storage.local) {
+      const nedbOptions: Nedb.DataStoreOptions = {
+        filename: config.storage.local.path,
+        inMemoryOnly: config.storage.local.in_memory
+      };
+      const collectionFactory = CollectionFactoryFactory.createNedb(nedbOptions);
+      return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
+    }
+    else if (config.storage.mongo) {
+      const mongoClient = new MongoClient(
+        config.storage.mongo,
+        globalLogger);
+      const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient);
+      return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
+    }
+
+    return BluebirdPromise.reject(new Error("Storage backend incorrectly configured."));
+  }
+}
+
+export class ServerVariablesInitializer {
+  static createUsersDatabase(
+    config: Configuration.Configuration,
+    deps: GlobalDependencies)
+    : IUsersDatabase {
+
+    if (config.authentication_backend.ldap) {
+      const ldapConfig = config.authentication_backend.ldap;
+      return new LdapUsersDatabase(
+        new SessionFactory(
+          ldapConfig,
+          new ConnectorFactory(ldapConfig, deps.ldapjs),
+          deps.winston
+        ),
+        ldapConfig
+      );
+    }
+    else if (config.authentication_backend.file) {
+      return new FileUsersDatabase(config.authentication_backend.file);
+    }
+  }
+
+  static initialize(
+    config: Configuration.Configuration,
+    globalLogger: IGlobalLogger,
+    requestLogger: IRequestLogger,
+    deps: GlobalDependencies)
+    : BluebirdPromise<ServerVariables> {
+
+    const mailSenderBuilder =
+      new MailSenderBuilder(Nodemailer);
+    const notifier = NotifierFactory.build(
+      config.notifier, mailSenderBuilder);
+    const authorizer = new Authorizer(config.access_control, deps.winston);
+    const totpHandler = new TotpHandler(deps.speakeasy);
+    const usersDatabase = this.createUsersDatabase(
+      config, deps);
+
+    return UserDataStoreFactory.create(config, globalLogger)
+      .then(function (userDataStore: UserDataStore) {
+        const regulator = new Regulator(userDataStore, config.regulation.max_retries,
+          config.regulation.find_time, config.regulation.ban_time);
+
+        const variables: ServerVariables = {
+          authorizer: authorizer,
+          config: config,
+          usersDatabase: usersDatabase,
+          logger: requestLogger,
+          notifier: notifier,
+          regulator: regulator,
+          totpHandler: totpHandler,
+          u2f: deps.u2f,
+          userDataStore: userDataStore
+        };
+        return BluebirdPromise.resolve(variables);
+      });
+  }
+}
diff --git a/themes/triangles/server/src/lib/ServerVariablesMockBuilder.spec.ts b/themes/triangles/server/src/lib/ServerVariablesMockBuilder.spec.ts
new file mode 100644
index 00000000..7874702a
--- /dev/null
+++ b/themes/triangles/server/src/lib/ServerVariablesMockBuilder.spec.ts
@@ -0,0 +1,87 @@
+import { ServerVariables } from "./ServerVariables";
+
+import { Configuration } from "./configuration/schema/Configuration";
+import { IUsersDatabaseStub } from "./authentication/backends/IUsersDatabaseStub.spec";
+import { AuthorizerStub } from "./authorization/AuthorizerStub.spec";
+import { RequestLoggerStub } from "./logging/RequestLoggerStub.spec";
+import { NotifierStub } from "./notifiers/NotifierStub.spec";
+import { RegulatorStub } from "./regulation/RegulatorStub.spec";
+import { TotpHandlerStub } from "./authentication/totp/TotpHandlerStub.spec";
+import { UserDataStoreStub } from "./storage/UserDataStoreStub.spec";
+import { U2fHandlerStub } from "./authentication/u2f/U2fHandlerStub.spec";
+
+export interface ServerVariablesMock {
+  authorizer: AuthorizerStub;
+  config: Configuration;
+  usersDatabase: IUsersDatabaseStub;
+  logger: RequestLoggerStub;
+  notifier: NotifierStub;
+  regulator: RegulatorStub;
+  totpHandler: TotpHandlerStub;
+  userDataStore: UserDataStoreStub;
+  u2f: U2fHandlerStub;
+}
+
+export class ServerVariablesMockBuilder {
+  static build(enableLogging?: boolean): { variables: ServerVariables, mocks: ServerVariablesMock} {
+    const mocks: ServerVariablesMock = {
+      authorizer: new AuthorizerStub(),
+      config: {
+        access_control: {},
+        totp: {
+          issuer: "authelia.com"
+        },
+        authentication_backend: {
+          ldap: {
+            url: "ldap://ldap",
+            base_dn: "dc=example,dc=com",
+            user: "user",
+            password: "password",
+            mail_attribute: "mail",
+            additional_users_dn: "ou=users",
+            additional_groups_dn: "ou=groups",
+            users_filter: "cn={0}",
+            groups_filter: "member={dn}",
+            group_name_attribute: "cn"
+          },
+        },
+        logs_level: "debug",
+        notifier: {},
+        port: 8080,
+        regulation: {
+          ban_time: 50,
+          find_time: 50,
+          max_retries: 3
+        },
+        session: {
+          secret: "my_secret",
+          domain: "mydomain"
+        },
+        storage: {}
+      },
+      usersDatabase: new IUsersDatabaseStub(),
+      logger: new RequestLoggerStub(enableLogging),
+      notifier: new NotifierStub(),
+      regulator: new RegulatorStub(),
+      totpHandler: new TotpHandlerStub(),
+      userDataStore: new UserDataStoreStub(),
+      u2f: new U2fHandlerStub()
+    };
+    const vars: ServerVariables = {
+      authorizer: mocks.authorizer,
+      config: mocks.config,
+      usersDatabase: mocks.usersDatabase,
+      logger: mocks.logger,
+      notifier: mocks.notifier,
+      regulator: mocks.regulator,
+      totpHandler: mocks.totpHandler,
+      userDataStore: mocks.userDataStore,
+      u2f: mocks.u2f
+    };
+
+    return {
+      variables: vars,
+      mocks: mocks
+    };
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/Level.ts b/themes/triangles/server/src/lib/authentication/Level.ts
new file mode 100644
index 00000000..57b6a234
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/Level.ts
@@ -0,0 +1,5 @@
+export enum Level {
+  NOT_AUTHENTICATED = 0,
+  ONE_FACTOR = 1,
+  TWO_FACTOR = 2
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/GroupsAndEmails.ts b/themes/triangles/server/src/lib/authentication/backends/GroupsAndEmails.ts
new file mode 100644
index 00000000..3434ba66
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/GroupsAndEmails.ts
@@ -0,0 +1,5 @@
+
+export interface GroupsAndEmails {
+  groups: string[];
+  emails: string[];
+}
diff --git a/themes/triangles/server/src/lib/authentication/backends/IUsersDatabase.ts b/themes/triangles/server/src/lib/authentication/backends/IUsersDatabase.ts
new file mode 100644
index 00000000..d7fa13b7
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/IUsersDatabase.ts
@@ -0,0 +1,10 @@
+import Bluebird = require("bluebird");
+
+import { GroupsAndEmails } from "./GroupsAndEmails";
+
+export interface IUsersDatabase {
+  checkUserPassword(username: string, password: string): Bluebird<GroupsAndEmails>;
+  getEmails(username: string): Bluebird<string[]>;
+  getGroups(username: string): Bluebird<string[]>;
+  updatePassword(username: string, newPassword: string): Bluebird<void>;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/IUsersDatabaseStub.spec.ts b/themes/triangles/server/src/lib/authentication/backends/IUsersDatabaseStub.spec.ts
new file mode 100644
index 00000000..19341a5d
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/IUsersDatabaseStub.spec.ts
@@ -0,0 +1,35 @@
+import Bluebird = require("bluebird");
+import Sinon = require("sinon");
+
+import { IUsersDatabase } from "./IUsersDatabase";
+import { GroupsAndEmails } from "./GroupsAndEmails";
+
+export class IUsersDatabaseStub implements IUsersDatabase {
+  checkUserPasswordStub: Sinon.SinonStub;
+  getEmailsStub: Sinon.SinonStub;
+  getGroupsStub: Sinon.SinonStub;
+  updatePasswordStub: Sinon.SinonStub;
+
+  constructor() {
+    this.checkUserPasswordStub = Sinon.stub();
+    this.getEmailsStub = Sinon.stub();
+    this.getGroupsStub = Sinon.stub();
+    this.updatePasswordStub = Sinon.stub();
+  }
+
+  checkUserPassword(username: string, password: string): Bluebird<GroupsAndEmails> {
+    return this.checkUserPasswordStub(username, password);
+  }
+
+  getEmails(username: string): Bluebird<string[]> {
+    return this.getEmailsStub(username);
+  }
+
+  getGroups(username: string): Bluebird<string[]> {
+    return this.getGroupsStub(username);
+  }
+
+  updatePassword(username: string, newPassword: string): Bluebird<void> {
+    return this.updatePasswordStub(username, newPassword);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/file/FileUsersDatabase.spec.ts b/themes/triangles/server/src/lib/authentication/backends/file/FileUsersDatabase.spec.ts
new file mode 100644
index 00000000..a258a78f
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/file/FileUsersDatabase.spec.ts
@@ -0,0 +1,224 @@
+import Assert = require("assert");
+import Bluebird = require("bluebird");
+import Fs = require("fs");
+import Sinon = require("sinon");
+import Tmp = require("tmp");
+
+import { FileUsersDatabase } from "./FileUsersDatabase";
+import { FileUsersDatabaseConfiguration } from "../../../configuration/schema/FileUsersDatabaseConfiguration";
+import { HashGenerator } from "../../../utils/HashGenerator";
+
+const GOOD_DATABASE = `
+users:
+  john:
+    password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+    email: john.doe@authelia.com
+    groups:
+      - admins
+      - dev
+
+  harry:
+    password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+    emails: harry.potter@authelia.com
+    groups: []
+`;
+
+const BAD_HASH = `
+users:
+  john:
+    password: "{CRYPT}$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+    email: john.doe@authelia.com
+    groups:
+      - admins
+      - dev
+`;
+
+const NO_PASSWORD_DATABASE = `
+users:
+  john:
+    email: john.doe@authelia.com
+    groups:
+      - admins
+      - dev
+`;
+
+const NO_EMAIL_DATABASE = `
+users:
+  john:
+    password: "{CRYPT}$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+    groups:
+      - admins
+      - dev
+`;
+
+const SINGLE_USER_DATABASE = `
+users:
+  john:
+    password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
+    email: john.doe@authelia.com
+    groups:
+      - admins
+      - dev
+`
+
+function createTmpFileFrom(yaml: string) {
+  const tmpFileAsync = Bluebird.promisify(Tmp.file);
+  return tmpFileAsync()
+    .then((path: string) => {
+      Fs.writeFileSync(path, yaml, "utf-8");
+      return Bluebird.resolve(path);
+    });
+}
+
+describe("authentication/backends/file/FileUsersDatabase", function() {
+  let configuration: FileUsersDatabaseConfiguration;
+
+  describe("checkUserPassword", () => {
+    describe("good config", () => {
+      beforeEach(() => {
+        return createTmpFileFrom(GOOD_DATABASE)
+          .then((path: string) => configuration = {
+            path: path
+          });
+      });
+
+      it("should succeed", () => {
+          const usersDatabase = new FileUsersDatabase(configuration);
+          return usersDatabase.checkUserPassword("john", "password")
+            .then((groupsAndEmails) => {
+              Assert.deepEqual(groupsAndEmails.groups, ["admins", "dev"]);
+              Assert.deepEqual(groupsAndEmails.emails, ["john.doe@authelia.com"]);
+            });
+      });
+
+      it("should fail when password is wrong", () => {
+          const usersDatabase = new FileUsersDatabase(configuration);
+          return usersDatabase.checkUserPassword("john", "bad_password")
+            .then(() => Bluebird.reject(new Error("should not be here.")))
+            .catch((err) => {
+              return Bluebird.resolve();
+            });
+      });
+
+      it("should fail when user does not exist", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.checkUserPassword("no_user", "password")
+          .then(() => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => {
+            return Bluebird.resolve();
+          });
+      });
+    });
+
+    describe("bad hash", () => {
+      beforeEach(() => {
+        return createTmpFileFrom(GOOD_DATABASE)
+          .then((path: string) => configuration = {
+            path: path
+          });
+      });
+
+      it("should fail when hash is wrong", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.checkUserPassword("john", "password")
+          .then(() => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => {
+            return Bluebird.resolve();
+          });
+      });
+    });
+
+    describe("no password", () => {
+      beforeEach(() => {
+        return createTmpFileFrom(NO_PASSWORD_DATABASE)
+          .then((path: string) => configuration = {
+            path: path
+          });
+      });
+
+      it("should fail", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.checkUserPassword("john", "password")
+          .then(() => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => {
+            return Bluebird.resolve();
+          });
+      });
+    });
+  });
+
+  describe("getEmails", () => {
+    describe("good config", () => {
+      beforeEach(() => {
+        return createTmpFileFrom(GOOD_DATABASE)
+          .then((path: string) => configuration = {
+            path: path
+          });
+      });
+
+      it("should succeed", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.getEmails("john")
+          .then((emails) => {
+            Assert.deepEqual(emails, ["john.doe@authelia.com"]);
+          });
+      });
+
+      it("should fail when user does not exist", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.getEmails("no_user")
+          .then(() => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => {
+            return Bluebird.resolve();
+          });
+      });
+    });
+
+    describe("no email provided", () => {
+      beforeEach(() => {
+        return createTmpFileFrom(NO_EMAIL_DATABASE)
+          .then((path: string) => configuration = {
+            path: path
+          });
+      });
+
+      it("should fail", () => {
+        const usersDatabase = new FileUsersDatabase(configuration);
+        return usersDatabase.getEmails("john")
+          .then(() => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => {
+            return Bluebird.resolve();
+          });
+      });
+    });
+  });
+
+  describe("updatePassword", () => {
+    beforeEach(() => {
+      return createTmpFileFrom(SINGLE_USER_DATABASE)
+        .then((path: string) => configuration = {
+          path: path
+        });
+    });
+
+    it("should succeed", () => {
+      const usersDatabase = new FileUsersDatabase(configuration);
+      const NEW_HASH = "{CRYPT}$6$rounds=500000$Qw6MhgADvLyYMEq9$ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+      const stub = Sinon.stub(HashGenerator, "ssha512").returns(Bluebird.resolve(NEW_HASH));
+      return usersDatabase.updatePassword("john", "mypassword")
+        .then(() => {
+          const content = Fs.readFileSync(configuration.path, "utf-8");
+          const matches = content.match(/password: '(.+)'/);
+          Assert.equal(matches[1], NEW_HASH);
+        })
+        .finally(() => stub.restore());
+    });
+
+    it("should fail when user does not exist", () => {
+      const usersDatabase = new FileUsersDatabase(configuration);
+      return usersDatabase.updatePassword("bad_user", "mypassword")
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch(() => Bluebird.resolve());
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/file/FileUsersDatabase.ts b/themes/triangles/server/src/lib/authentication/backends/file/FileUsersDatabase.ts
new file mode 100644
index 00000000..d34dde21
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/file/FileUsersDatabase.ts
@@ -0,0 +1,182 @@
+import Bluebird = require("bluebird");
+import Fs = require("fs");
+import Yaml = require("yamljs");
+
+import { FileUsersDatabaseConfiguration }
+  from "../../../configuration/schema/FileUsersDatabaseConfiguration";
+import { GroupsAndEmails } from "../GroupsAndEmails";
+import { IUsersDatabase } from "../IUsersDatabase";
+import { HashGenerator } from "../../../utils/HashGenerator";
+import { ReadWriteQueue } from "./ReadWriteQueue";
+
+const loadAsync = Bluebird.promisify(Yaml.load);
+
+export class FileUsersDatabase implements IUsersDatabase {
+  private configuration: FileUsersDatabaseConfiguration;
+  private queue: ReadWriteQueue;
+
+  constructor(configuration: FileUsersDatabaseConfiguration) {
+    this.configuration = configuration;
+    this.queue = new ReadWriteQueue(this.configuration.path);
+  }
+
+  /**
+   * Read database from file.
+   * It enqueues the read task so that it is scheduled
+   * between other reads and writes.
+   */
+  private readDatabase(): Bluebird<any> {
+    return new Bluebird<string>((resolve, reject) => {
+      this.queue.read((err: Error, data: string) => {
+        if (err) {
+          reject(err);
+          return;
+        }
+        resolve(data);
+        this.queue.next();
+      });
+    })
+    .then((content) => {
+      const database = Yaml.parse(content);
+      if (!database) {
+        return Bluebird.reject(new Error("Unable to parse YAML file."));
+      }
+      return Bluebird.resolve(database);
+    });
+  }
+
+  /**
+   * Checks the user exists in the database.
+   */
+  private checkUserExists(
+    database: any,
+    username: string)
+    : Bluebird<void> {
+    if (!(username in database.users)) {
+      return Bluebird.reject(
+        new Error(`User ${username} does not exist in database.`));
+    }
+    return Bluebird.resolve();
+  }
+
+  /**
+   * Check the password of a given user.
+   */
+  private checkPassword(
+    database: any,
+    username: string,
+    password: string)
+    : Bluebird<void> {
+    const storedHash: string = database.users[username].password;
+    const matches = storedHash.match(/rounds=([0-9]+)\$([a-zA-z0-9]+)\$/);
+    if (!(matches && matches.length == 3)) {
+      return Bluebird.reject(new Error("Unable to detect the hash salt and rounds. " +
+        "Make sure the password is hashed with SSHA512."));
+    }
+
+    const rounds: number = parseInt(matches[1]);
+    const salt = matches[2];
+
+    return HashGenerator.ssha512(password, rounds, salt)
+      .then((hash: string) => {
+        if (hash !== storedHash) {
+          return Bluebird.reject(new Error("Wrong username/password."));
+        }
+        return Bluebird.resolve();
+      });
+  }
+
+  /**
+   * Retrieve email addresses of a given user.
+   */
+  private retrieveEmails(
+    database: any,
+    username: string)
+    : Bluebird<string[]> {
+    if (!("email" in database.users[username])) {
+      return Bluebird.reject(
+        new Error(`User ${username} has no email address.`));
+    }
+    return Bluebird.resolve(
+      [database.users[username].email]);
+  }
+
+  private retrieveGroups(
+    database: any,
+    username: string)
+    : Bluebird<string[]> {
+    if (!("groups" in database.users[username])) {
+      return Bluebird.resolve([]);
+    }
+    return Bluebird.resolve(
+      database.users[username].groups);
+  }
+
+  private replacePassword(
+    database: any,
+    username: string,
+    newPassword: string)
+    : Bluebird<void> {
+    const that = this;
+    return HashGenerator.ssha512(newPassword)
+      .then((hash) => {
+        database.users[username].password = hash;
+        const str = Yaml.stringify(database, 4, 2);
+        return Bluebird.resolve(str);
+      })
+      .then((content: string) => {
+        return new Bluebird((resolve, reject) => {
+          that.queue.write(content, (err) => {
+            if (err) {
+              return reject(err);
+            }
+            resolve();
+            that.queue.next();
+          });
+        });
+      });
+  }
+
+  checkUserPassword(
+    username: string,
+    password: string)
+    : Bluebird<GroupsAndEmails> {
+    return this.readDatabase()
+      .then((database) => {
+        return this.checkUserExists(database, username)
+          .then(() => this.checkPassword(database, username, password))
+          .then(() => {
+            return Bluebird.join(
+              this.retrieveEmails(database, username),
+              this.retrieveGroups(database, username)
+            ).spread((emails: string[], groups: string[]) => {
+              return { emails: emails, groups: groups };
+            });
+          });
+        });
+  }
+
+  getEmails(username: string): Bluebird<string[]> {
+    return this.readDatabase()
+      .then((database) => {
+        return this.checkUserExists(database, username)
+          .then(() => this.retrieveEmails(database, username));
+      });
+  }
+
+  getGroups(username: string): Bluebird<string[]> {
+    return this.readDatabase()
+      .then((database) => {
+        return this.checkUserExists(database, username)
+          .then(() => this.retrieveGroups(database, username));
+      });
+  }
+
+  updatePassword(username: string, newPassword: string): Bluebird<void> {
+    return this.readDatabase()
+      .then((database) => {
+        return this.checkUserExists(database, username)
+          .then(() => this.replacePassword(database, username, newPassword));
+      });
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/file/ReadWriteQueue.ts b/themes/triangles/server/src/lib/authentication/backends/file/ReadWriteQueue.ts
new file mode 100644
index 00000000..957ddaec
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/file/ReadWriteQueue.ts
@@ -0,0 +1,60 @@
+import Fs = require("fs");
+
+type Callback = (err: Error, data?: string) => void;
+type ContentAndCallback = [string, Callback] | [string, string, Callback];
+
+/**
+ * WriteQueue is a queue synchronizing writes to a file.
+ *
+ * Example of use:
+ *
+ * queue.add(mycontent, (err) => {
+ *    // do whatever you want here.
+ *    queue.next();
+ * })
+ */
+export class ReadWriteQueue {
+  private filePath: string;
+  private queue: ContentAndCallback[];
+
+  constructor (filePath: string) {
+    this.queue = [];
+    this.filePath = filePath;
+  }
+
+  next () {
+    if (this.queue.length === 0)
+      return;
+
+    const task = this.queue[0];
+
+    if (task[0] == "write") {
+      Fs.writeFile(this.filePath, task[1], "utf-8", (err) => {
+        this.queue.shift();
+        const cb = task[2] as Callback;
+        cb(err);
+      });
+    }
+    else if (task[0] == "read") {
+      Fs.readFile(this.filePath, { encoding: "utf-8"} , (err, data) => {
+        this.queue.shift();
+        const cb = task[1] as Callback;
+        cb(err, data);
+      });
+    }
+  }
+
+  write (content: string, cb: Callback) {
+    this.queue.push(["write", content, cb]);
+    if (this.queue.length === 1) {
+      this.next();
+    }
+  }
+
+  read (cb: Callback) {
+    this.queue.push(["read", cb]);
+    if (this.queue.length === 1) {
+      this.next();
+    }
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/ISession.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/ISession.ts
new file mode 100644
index 00000000..da2c7443
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/ISession.ts
@@ -0,0 +1,12 @@
+
+import BluebirdPromise = require("bluebird");
+
+export interface ISession {
+  open(): BluebirdPromise<void>;
+  close(): BluebirdPromise<void>;
+
+  searchUserDn(username: string): BluebirdPromise<string>;
+  searchEmails(username: string): BluebirdPromise<string[]>;
+  searchGroups(username: string): BluebirdPromise<string[]>;
+  modifyPassword(username: string, newPassword: string): BluebirdPromise<void>;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/ISessionFactory.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/ISessionFactory.ts
new file mode 100644
index 00000000..014d1eea
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/ISessionFactory.ts
@@ -0,0 +1,6 @@
+
+import { ISession } from "./ISession";
+
+export interface ISessionFactory {
+  create(userDN: string, password: string): ISession;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.spec.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.spec.ts
new file mode 100644
index 00000000..f4a6e630
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.spec.ts
@@ -0,0 +1,386 @@
+import Assert = require("assert");
+import Bluebird = require("bluebird");
+
+import { LdapUsersDatabase } from "./LdapUsersDatabase";
+
+import { SessionFactoryStub } from "./SessionFactoryStub.spec";
+import { SessionStub } from "./SessionStub.spec";
+
+const ADMIN_USER_DN = "cn=admin,dc=example,dc=com";
+const ADMIN_PASSWORD = "password";
+
+describe("ldap/connector/LdapUsersDatabase", function() {
+  let sessionFactory: SessionFactoryStub;
+  let usersDatabase: LdapUsersDatabase;
+
+  const USERNAME = "user";
+  const PASSWORD = "pass";
+  const NEW_PASSWORD = "pass2";
+
+  const LDAP_CONFIG = {
+    url: "http://localhost:324",
+    additional_users_dn: "ou=users",
+    additional_groups_dn: "ou=groups",
+    base_dn: "dc=example,dc=com",
+    users_filter: "cn={0}",
+    groups_filter: "member={0}",
+    mail_attribute: "mail",
+    group_name_attribute: "cn",
+    user: ADMIN_USER_DN,
+    password: ADMIN_PASSWORD
+  };
+
+  beforeEach(function() {
+    sessionFactory = new SessionFactoryStub();
+    usersDatabase = new LdapUsersDatabase(sessionFactory, LDAP_CONFIG);
+  })
+
+  describe("checkUserPassword", function() {
+    it("should return groups and emails when user/password matches", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+      const emails = ["email1", "email2"];
+      const groups = ["group1", "group2"];
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.resolve());
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
+      adminSession.searchEmailsStub.withArgs(USERNAME).returns(Bluebird.resolve(emails));
+      adminSession.searchGroupsStub.withArgs(USERNAME).returns(Bluebird.resolve(groups));
+      
+      userSession.openStub.returns(Bluebird.resolve());
+      userSession.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then((groupsAndEmails) => {
+          Assert.deepEqual(groupsAndEmails.groups, groups);
+          Assert.deepEqual(groupsAndEmails.emails, emails);
+        })
+    });
+
+    it("should fail when username/password is wrong", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.resolve());
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
+      
+      userSession.openStub.returns(Bluebird.reject(new Error("Failed binding")));
+      userSession.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(userSession.closeStub.called);
+          Assert(adminSession.closeStub.called);
+          return Bluebird.resolve();
+        })
+    });
+
+    it("should fail when admin binding fails", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.reject(new Error("Failed binding")));
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(userSession.closeStub.notCalled);
+          Assert(adminSession.closeStub.called);
+          return Bluebird.resolve();
+        })
+    });
+
+    it("should fail when search for user dn fails", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.resolve());
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.reject(new Error("Failed searching user dn")));
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(userSession.closeStub.notCalled);
+          Assert(adminSession.closeStub.called);
+          return Bluebird.resolve();
+        })
+    });
+
+    it("should fail when groups retrieval fails", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+      const emails = ["email1", "email2"];
+      const groups = ["group1", "group2"];
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.resolve());
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
+      adminSession.searchEmailsStub.withArgs(USERNAME)
+        .returns(Bluebird.resolve(emails));
+      adminSession.searchGroupsStub.withArgs(USERNAME)
+        .returns(Bluebird.reject(new Error("Failed retrieving groups")));
+      
+      userSession.openStub.returns(Bluebird.resolve());
+      userSession.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then((groupsAndEmails) => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(userSession.closeStub.called);
+          Assert(adminSession.closeStub.called);
+        })
+    });
+
+    it("should fail when emails retrieval fails", function() {
+      const USER_DN = `cn=${USERNAME},dc=example,dc=com`;
+      const emails = ["email1", "email2"];
+      const groups = ["group1", "group2"];
+
+      const adminSession = new SessionStub();
+      const userSession = new SessionStub();
+
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(adminSession);
+      sessionFactory.createStub.withArgs(USER_DN, PASSWORD).returns(userSession);
+
+      adminSession.openStub.returns(Bluebird.resolve());
+      adminSession.closeStub.returns(Bluebird.resolve());
+      adminSession.searchUserDnStub.returns(Bluebird.resolve(USER_DN));
+      adminSession.searchEmailsStub.withArgs(USERNAME)
+        .returns(Bluebird.reject(new Error("Emails retrieval failed")));
+      adminSession.searchGroupsStub.withArgs(USERNAME)
+        .returns(Bluebird.resolve(groups));
+      
+      userSession.openStub.returns(Bluebird.resolve());
+      userSession.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.checkUserPassword(USERNAME, PASSWORD)
+        .then((groupsAndEmails) => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(userSession.closeStub.called);
+          Assert(adminSession.closeStub.called);
+        })
+    });
+  });
+
+  describe("getEmails", function() {
+    it("should succefully retrieves email", () => {
+      const emails = ["email1", "email2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.closeStub.returns(Bluebird.resolve());
+      session.searchEmailsStub.returns(Bluebird.resolve(emails));
+
+      return usersDatabase.getEmails(USERNAME)
+        .then((foundEmails) => {
+          Assert(session.closeStub.called);
+          Assert.deepEqual(foundEmails, emails);
+        })
+    });
+
+    it("should fail when binding fails", () => {
+      const emails = ["email1", "email2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.reject(new Error("Binding failed")));
+
+      return usersDatabase.getEmails(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when unbinding fails", () => {
+      const emails = ["email1", "email2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.searchEmailsStub.returns(Bluebird.resolve(emails));
+      session.closeStub.returns(Bluebird.reject(new Error("Unbinding failed")));
+
+      return usersDatabase.getEmails(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when search fails", () => {
+      const emails = ["email1", "email2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.searchEmailsStub.returns(Bluebird.reject(new Error("Search failed")));
+      session.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.getEmails(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+  });
+
+
+  describe("getGroups", function() {
+    it("should succefully retrieves groups", () => {
+      const groups = ["group1", "group2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.closeStub.returns(Bluebird.resolve());
+      session.searchGroupsStub.returns(Bluebird.resolve(groups));
+
+      return usersDatabase.getGroups(USERNAME)
+        .then((foundGroups) => {
+          Assert(session.closeStub.called);
+          Assert.deepEqual(foundGroups, groups);
+        })
+    });
+
+    it("should fail when binding fails", () => {
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.reject(new Error("Binding failed")));
+
+      return usersDatabase.getGroups(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when unbinding fails", () => {
+      const groups = ["group1", "group2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.searchGroupsStub.returns(Bluebird.resolve(groups));
+      session.closeStub.returns(Bluebird.reject(new Error("Unbinding failed")));
+
+      return usersDatabase.getGroups(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when search fails", () => {
+      const groups = ["group1", "group2"];
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.searchGroupsStub.returns(Bluebird.reject(new Error("Search failed")));
+      session.closeStub.returns(Bluebird.resolve());
+
+      return usersDatabase.getGroups(USERNAME)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch((err) => {
+          Assert(session.closeStub.called);
+        })
+    });
+  });
+
+
+  describe("updatePassword", function() {
+    it("should successfully update password", () => {
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.closeStub.returns(Bluebird.resolve());
+      session.modifyPasswordStub.returns(Bluebird.resolve());
+
+      return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
+        .then(() => {
+          Assert(session.modifyPasswordStub.calledWith(USERNAME, NEW_PASSWORD));
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when binding fails", () => {
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.reject(new Error("Binding failed")));
+      session.closeStub.returns(Bluebird.resolve());
+      session.modifyPasswordStub.returns(Bluebird.resolve());
+
+      return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch(() => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when update fails", () => {
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.closeStub.returns(Bluebird.reject(new Error("Update failed")));
+      session.modifyPasswordStub.returns(Bluebird.resolve());
+
+      return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch(() => {
+          Assert(session.closeStub.called);
+        })
+    });
+
+    it("should fail when unbind fails", () => {
+      const session = new SessionStub();
+      sessionFactory.createStub.withArgs(ADMIN_USER_DN, ADMIN_PASSWORD).returns(session);
+
+      session.openStub.returns(Bluebird.resolve());
+      session.closeStub.returns(Bluebird.resolve());
+      session.modifyPasswordStub.returns(Bluebird.reject(new Error("Unbind failed")));
+
+      return usersDatabase.updatePassword(USERNAME, NEW_PASSWORD)
+        .then(() => Bluebird.reject(new Error("should not be here")))
+        .catch(() => {
+          Assert(session.closeStub.called);
+        })
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.ts
new file mode 100644
index 00000000..edda62ec
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/LdapUsersDatabase.ts
@@ -0,0 +1,107 @@
+import Bluebird = require("bluebird");
+import { IUsersDatabase } from "../IUsersDatabase";
+import { ISessionFactory } from "./ISessionFactory";
+import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
+import { ISession } from "./ISession";
+import { GroupsAndEmails } from "../GroupsAndEmails";
+import Exceptions = require("../../../Exceptions");
+
+type SessionCallback<T> = (session: ISession) => Bluebird<T>;
+
+export class LdapUsersDatabase implements IUsersDatabase {
+  private sessionFactory: ISessionFactory;
+  private configuration: LdapConfiguration;
+
+  constructor(
+    sessionFactory: ISessionFactory,
+    configuration: LdapConfiguration) {
+    this.sessionFactory = sessionFactory;
+    this.configuration = configuration;
+  }
+
+  private withSession<T>(
+    username: string,
+    password: string,
+    cb: SessionCallback<T>): Bluebird<T> {
+    const session = this.sessionFactory.create(username, password);
+    return session.open()
+      .then(() => cb(session))
+      .finally(() => session.close());
+  }
+
+  checkUserPassword(username: string, password: string): Bluebird<GroupsAndEmails> {
+    const that = this;
+    function verifyUserPassword(userDN: string) {
+      return that.withSession<void>(
+        userDN,
+        password,
+        (session) => Bluebird.resolve()
+      );
+    }
+
+    function getInfo(session: ISession) {
+        return Bluebird.join(
+          session.searchGroups(username),
+          session.searchEmails(username)
+        )
+        .spread((groups: string[], emails: string[]) => {
+          return { groups: groups, emails: emails };
+        });
+    }
+
+    return that.withSession(
+      that.configuration.user,
+      that.configuration.password,
+      (session) => {
+        return session.searchUserDn(username)
+          .then(verifyUserPassword)
+          .then(() => getInfo(session));
+      })
+      .catch((err) =>
+        Bluebird.reject(new Exceptions.LdapError(err.message)));
+  }
+
+  getEmails(username: string): Bluebird<string[]> {
+    const that = this;
+    return that.withSession(
+      that.configuration.user,
+      that.configuration.password,
+      (session) => {
+        return session.searchEmails(username);
+      }
+    )
+    .catch((err) =>
+      Bluebird.reject(new Exceptions.LdapError("Failed during email retrieval: " + err.message))
+    );
+  }
+
+  getGroups(username: string): Bluebird<string[]> {
+    const that = this;
+    return that.withSession(
+      that.configuration.user,
+      that.configuration.password,
+      (session) => {
+        return session.searchGroups(username);
+      }
+    )
+    .catch((err) =>
+      Bluebird.reject(new Exceptions.LdapError("Failed during email retrieval: " + err.message))
+    );
+  }
+
+  updatePassword(username: string, newPassword: string): Bluebird<void> {
+    const that = this;
+    return that.withSession(
+      that.configuration.user,
+      that.configuration.password,
+      (session) => {
+        return session.modifyPassword(username, newPassword);
+      }
+    )
+    .catch(function (err: Error) {
+      return Bluebird.reject(
+        new Exceptions.LdapError(
+          "Error while updating password: " + err.message));
+    });
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/SafeSession.spec.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/SafeSession.spec.ts
new file mode 100644
index 00000000..9dedfcb7
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/SafeSession.spec.ts
@@ -0,0 +1,76 @@
+import BluebirdPromise = require("bluebird");
+import { SessionStub } from "./SessionStub.spec";
+import { SafeSession } from "./SafeSession";
+
+describe("ldap/SanitizedClient", function () {
+  let client: SafeSession;
+
+  beforeEach(function () {
+    const clientStub = new SessionStub();
+    clientStub.searchUserDnStub.onCall(0).returns(BluebirdPromise.resolve());
+    clientStub.searchGroupsStub.onCall(0).returns(BluebirdPromise.resolve());
+    clientStub.searchEmailsStub.onCall(0).returns(BluebirdPromise.resolve());
+    clientStub.modifyPasswordStub.onCall(0).returns(BluebirdPromise.resolve());
+    client = new SafeSession(clientStub);
+  });
+
+  describe("special chars are used", function () {
+    it("should fail when special chars are used in searchUserDn", function () {
+      // potential ldap injection";
+      return client.searchUserDn("cn=dummy_user,ou=groupgs")
+        .then(function () {
+          return BluebirdPromise.reject(new Error("Should not be here."));
+        }, function () {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail when special chars are used in searchGroups", function () {
+      // potential ldap injection";
+      return client.searchGroups("cn=dummy_user,ou=groupgs")
+        .then(function () {
+          return BluebirdPromise.reject(new Error("Should not be here."));
+        }, function () {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail when special chars are used in searchEmails", function () {
+      // potential ldap injection";
+      return client.searchEmails("cn=dummy_user,ou=groupgs")
+        .then(function () {
+          return BluebirdPromise.reject(new Error("Should not be here."));
+        }, function () {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail when special chars are used in modifyPassword", function () {
+      // potential ldap injection";
+      return client.modifyPassword("cn=dummy_user,ou=groupgs", "abc")
+        .then(function () {
+          return BluebirdPromise.reject(new Error("Should not be here."));
+        }, function () {
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+
+  describe("no special chars are used", function() {
+    it("should succeed when no special chars are used in searchUserDn", function () {
+      return client.searchUserDn("dummy_user");
+    });
+
+    it("should succeed when no special chars are used in searchGroups", function () {
+      return client.searchGroups("dummy_user");
+    });
+
+    it("should succeed when no special chars are used in searchEmails", function () {
+      return client.searchEmails("dummy_user");
+    });
+
+    it("should succeed when no special chars are used in modifyPassword", function () {
+      return client.modifyPassword("dummy_user", "abc");
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/SafeSession.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/SafeSession.ts
new file mode 100644
index 00000000..57220906
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/SafeSession.ts
@@ -0,0 +1,62 @@
+import BluebirdPromise = require("bluebird");
+import { ISession } from "./ISession";
+import { Sanitizer } from "./Sanitizer";
+
+const SPECIAL_CHAR_USED_MESSAGE = "Special character used in LDAP query.";
+
+
+export class SafeSession implements ISession {
+  private sesion: ISession;
+
+  constructor(sesion: ISession) {
+    this.sesion = sesion;
+  }
+
+  open(): BluebirdPromise<void> {
+    return this.sesion.open();
+  }
+
+  close(): BluebirdPromise<void> {
+    return this.sesion.close();
+  }
+
+  searchGroups(username: string): BluebirdPromise<string[]> {
+    try {
+      const sanitizedUsername = Sanitizer.sanitize(username);
+      return this.sesion.searchGroups(sanitizedUsername);
+    }
+    catch (e) {
+      return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
+    }
+  }
+
+  searchUserDn(username: string): BluebirdPromise<string> {
+    try {
+      const sanitizedUsername = Sanitizer.sanitize(username);
+      return this.sesion.searchUserDn(sanitizedUsername);
+    }
+    catch (e) {
+      return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
+    }
+  }
+
+  searchEmails(username: string): BluebirdPromise<string[]> {
+    try {
+      const sanitizedUsername = Sanitizer.sanitize(username);
+      return this.sesion.searchEmails(sanitizedUsername);
+    }
+    catch (e) {
+      return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
+    }
+  }
+
+  modifyPassword(username: string, newPassword: string): BluebirdPromise<void> {
+    try {
+      const sanitizedUsername = Sanitizer.sanitize(username);
+      return this.sesion.modifyPassword(sanitizedUsername, newPassword);
+    }
+    catch (e) {
+      return BluebirdPromise.reject(new Error(SPECIAL_CHAR_USED_MESSAGE));
+    }
+  }
+}
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/Sanitizer.spec.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/Sanitizer.spec.ts
new file mode 100644
index 00000000..9dd33fed
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/Sanitizer.spec.ts
@@ -0,0 +1,25 @@
+import Assert = require("assert");
+import { Sanitizer } from "./Sanitizer";
+
+describe("ldap/InputsSanitizer", function () {
+  it("should fail when special characters are used", function () {
+    Assert.throws(() => { Sanitizer.sanitize("ab,c"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a\\bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a'bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a#bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a+bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a<bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a>bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a;bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a\"bc"); }, Error);
+    Assert.throws(() => { Sanitizer.sanitize("a=bc"); }, Error);
+  });
+
+  it("should return original string", function () {
+    Assert.equal(Sanitizer.sanitize("abcdef"), "abcdef");
+  });
+
+  it("should trim", function () {
+    Assert.throws(() => { Sanitizer.sanitize("    abc    "); }, Error);
+  });
+});
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/Sanitizer.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/Sanitizer.ts
new file mode 100644
index 00000000..be74132a
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/Sanitizer.ts
@@ -0,0 +1,25 @@
+
+// returns true for 1 or more matches, where 'a' is an array and 'b' is a search string or an array of multiple search strings
+function contains(a: string, character: string) {
+  // string match
+  return a.indexOf(character) > -1;
+}
+
+function containsOneOf(s: string, characters: string[]) {
+  return characters
+    .map((character: string) => { return contains(s, character); })
+    .reduce((acc: boolean, current: boolean) => { return acc || current; }, false);
+}
+
+export class Sanitizer {
+  static sanitize(input: string): string {
+    const forbiddenChars = [",", "\\", "'", "#", "+", "<", ">", ";", "\"", "="];
+    if (containsOneOf(input, forbiddenChars))
+      throw new Error("Input containing unsafe characters.");
+
+    if (input != input.trim())
+      throw new Error("Input has unexpected spaces.");
+
+    return input;
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/Session.spec.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/Session.spec.ts
new file mode 100644
index 00000000..d55f6a80
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/Session.spec.ts
@@ -0,0 +1,127 @@
+
+import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
+import { Session } from "./Session";
+import { ConnectorFactoryStub } from "./connector/ConnectorFactoryStub.spec";
+import { ConnectorStub } from "./connector/ConnectorStub.spec";
+
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+import Winston = require("winston");
+
+describe("ldap/Session", function () {
+  const USERNAME = "username";
+  const ADMIN_USER_DN = "cn=admin,dc=example,dc=com";
+  const ADMIN_PASSWORD = "password";
+
+  it("should replace {0} by username when searching for groups in LDAP", function () {
+    const options: LdapConfiguration = {
+      url: "ldap://ldap",
+      additional_users_dn: "ou=users",
+      additional_groups_dn: "ou=groups",
+      base_dn: "dc=example,dc=com",
+      users_filter: "cn={0}",
+      groups_filter: "member=cn={0},ou=users,dc=example,dc=com",
+      group_name_attribute: "cn",
+      mail_attribute: "mail",
+      user: "cn=admin,dc=example,dc=com",
+      password: "password"
+    };
+    const connectorStub = new ConnectorStub();
+    connectorStub.searchAsyncStub.returns(BluebirdPromise.resolve([{
+      cn: "group1"
+    }]));
+    const client = new Session(ADMIN_USER_DN, ADMIN_PASSWORD, options, connectorStub, Winston);
+
+    return client.searchGroups("user1")
+      .then(function () {
+        Assert.equal(connectorStub.searchAsyncStub.getCall(0).args[1].filter,
+          "member=cn=user1,ou=users,dc=example,dc=com");
+      });
+  });
+
+  it("should replace {dn} by user DN when searching for groups in LDAP", function () {
+    const USER_DN = "cn=user1,ou=users,dc=example,dc=com";
+    const options: LdapConfiguration = {
+      url: "ldap://ldap",
+      additional_users_dn: "ou=users",
+      additional_groups_dn: "ou=groups",
+      base_dn: "dc=example,dc=com",
+      users_filter: "cn={0}",
+      groups_filter: "member={dn}",
+      group_name_attribute: "cn",
+      mail_attribute: "mail",
+      user: "cn=admin,dc=example,dc=com",
+      password: "password"
+    };
+    const ldapClient = new ConnectorStub();
+
+    // Retrieve user DN
+    ldapClient.searchAsyncStub.withArgs("ou=users,dc=example,dc=com", {
+      scope: "sub",
+      sizeLimit: 1,
+      attributes: ["dn"],
+      filter: "cn=user1"
+    }).returns(BluebirdPromise.resolve([{
+      dn: USER_DN
+    }]));
+
+    // Retrieve groups
+    ldapClient.searchAsyncStub.withArgs("ou=groups,dc=example,dc=com", {
+      scope: "sub",
+      attributes: ["cn"],
+      filter: "member=" + USER_DN
+    }).returns(BluebirdPromise.resolve([{
+      cn: "group1"
+    }]));
+
+    const client = new Session(ADMIN_USER_DN, ADMIN_PASSWORD, options, ldapClient, Winston);
+
+    return client.searchGroups("user1")
+      .then(function (groups: string[]) {
+        Assert.deepEqual(groups, ["group1"]);
+      });
+  });
+
+  it("should retrieve mail from custom attribute", function () {
+    const USER_DN = "cn=user1,ou=users,dc=example,dc=com";
+    const options: LdapConfiguration = {
+      url: "ldap://ldap",
+      additional_users_dn: "ou=users",
+      additional_groups_dn: "ou=groups",
+      base_dn: "dc=example,dc=com",
+      users_filter: "cn={0}",
+      groups_filter: "member={dn}",
+      group_name_attribute: "cn",
+      mail_attribute: "custom_mail",
+      user: "cn=admin,dc=example,dc=com",
+      password: "password"
+    };
+    const connector = new ConnectorStub();
+    // Retrieve user DN
+    connector.searchAsyncStub.withArgs("ou=users,dc=example,dc=com", {
+      scope: "sub",
+      sizeLimit: 1,
+      attributes: ["dn"],
+      filter: "cn=user1"
+    }).returns(BluebirdPromise.resolve([{
+      dn: USER_DN
+    }]));
+
+    // Retrieve email
+    connector.searchAsyncStub.withArgs("cn=user1,ou=users,dc=example,dc=com", {
+      scope: "base",
+      sizeLimit: 1,
+      attributes: ["custom_mail"],
+    }).returns(BluebirdPromise.resolve([{
+      custom_mail: "user1@example.com"
+    }]));
+
+    const client = new Session(ADMIN_USER_DN, ADMIN_PASSWORD, options, connector, Winston);
+
+    return client.searchEmails("user1")
+      .then(function (emails: string[]) {
+        Assert.deepEqual(emails, ["user1@example.com"]);
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/Session.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/Session.ts
new file mode 100644
index 00000000..e0284b3c
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/Session.ts
@@ -0,0 +1,156 @@
+import BluebirdPromise = require("bluebird");
+import exceptions = require("../../../Exceptions");
+import { EventEmitter } from "events";
+import { ISession } from "./ISession";
+import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
+import { Winston } from "../../../../../types/Dependencies";
+import Util = require("util");
+import { HashGenerator } from "../../../utils/HashGenerator";
+import { IConnector } from "./connector/IConnector";
+
+export class Session implements ISession {
+  private userDN: string;
+  private password: string;
+  private connector: IConnector;
+  private logger: Winston;
+  private options: LdapConfiguration;
+
+  private groupsSearchBase: string;
+  private usersSearchBase: string;
+
+  constructor(userDN: string, password: string, options: LdapConfiguration,
+    connector: IConnector, logger: Winston) {
+    this.options = options;
+    this.logger = logger;
+    this.userDN = userDN;
+    this.password = password;
+    this.connector = connector;
+
+    this.groupsSearchBase = (this.options.additional_groups_dn)
+      ? Util.format("%s,%s", this.options.additional_groups_dn, this.options.base_dn)
+      : this.options.base_dn;
+
+    this.usersSearchBase = (this.options.additional_users_dn)
+      ? Util.format("%s,%s", this.options.additional_users_dn, this.options.base_dn)
+      : this.options.base_dn;
+  }
+
+  open(): BluebirdPromise<void> {
+    this.logger.debug("LDAP: Bind user '%s'", this.userDN);
+    return this.connector.bindAsync(this.userDN, this.password)
+      .error(function (err: Error) {
+        return BluebirdPromise.reject(new exceptions.LdapBindError(err.message));
+      });
+  }
+
+  close(): BluebirdPromise<void> {
+    this.logger.debug("LDAP: Unbind user '%s'", this.userDN);
+    return this.connector.unbindAsync()
+      .error(function (err: Error) {
+        return BluebirdPromise.reject(new exceptions.LdapBindError(err.message));
+      });
+  }
+
+  private createGroupsFilter(userGroupsFilter: string, username: string): BluebirdPromise<string> {
+    if (userGroupsFilter.indexOf("{0}") > 0) {
+      return BluebirdPromise.resolve(userGroupsFilter.replace("{0}", username));
+    }
+    else if (userGroupsFilter.indexOf("{dn}") > 0) {
+      return this.searchUserDn(username)
+        .then(function (userDN: string) {
+          return BluebirdPromise.resolve(userGroupsFilter.replace("{dn}", userDN));
+        });
+    }
+    return BluebirdPromise.resolve(userGroupsFilter);
+  }
+
+  searchGroups(username: string): BluebirdPromise<string[]> {
+    const that = this;
+    return this.createGroupsFilter(this.options.groups_filter, username)
+      .then(function (groupsFilter: string) {
+        that.logger.debug("Computed groups filter is %s", groupsFilter);
+        const query = {
+          scope: "sub",
+          attributes: [that.options.group_name_attribute],
+          filter: groupsFilter
+        };
+        return that.connector.searchAsync(that.groupsSearchBase, query);
+      })
+      .then(function (docs: { cn: string }[]) {
+        const groups = docs.map((doc: any) => { return doc.cn; });
+        that.logger.debug("LDAP: groups of user %s are [%s]", username, groups.join(","));
+        return BluebirdPromise.resolve(groups);
+      });
+  }
+
+  searchUserDn(username: string): BluebirdPromise<string> {
+    const that = this;
+    const filter = this.options.users_filter.replace("{0}", username);
+    this.logger.debug("Computed users filter is %s", filter);
+    const query = {
+      scope: "sub",
+      sizeLimit: 1,
+      attributes: ["dn"],
+      filter: filter
+    };
+
+    that.logger.debug("LDAP: searching for user dn of %s", username);
+    return that.connector.searchAsync(this.usersSearchBase, query)
+      .then(function (users: { dn: string }[]) {
+        if (users.length > 0) {
+          that.logger.debug("LDAP: retrieved user dn is %s", users[0].dn);
+          return BluebirdPromise.resolve(users[0].dn);
+        }
+        return BluebirdPromise.reject(new Error(
+          Util.format("No user DN found for user '%s'", username)));
+      });
+  }
+
+  searchEmails(username: string): BluebirdPromise<string[]> {
+    const that = this;
+    const query = {
+      scope: "base",
+      sizeLimit: 1,
+      attributes: [this.options.mail_attribute]
+    };
+
+    return this.searchUserDn(username)
+      .then(function (userDN) {
+        return that.connector.searchAsync(userDN, query);
+      })
+      .then(function (docs: { [mail_attribute: string]: string }[]) {
+        const emails: string[] = docs
+          .filter((d) => { return typeof d[that.options.mail_attribute] === "string"; })
+          .map((d) => { return d[that.options.mail_attribute]; });
+        that.logger.debug("LDAP: emails of user '%s' are %s", username, emails);
+        return BluebirdPromise.resolve(emails);
+      })
+      .catch(function (err: Error) {
+        return BluebirdPromise.reject(new exceptions.LdapError("Error while searching emails. " + err.stack));
+      });
+  }
+
+  modifyPassword(username: string, newPassword: string): BluebirdPromise<void> {
+    const that = this;
+    this.logger.debug("LDAP: update password of user '%s'", username);
+    return this.searchUserDn(username)
+      .then(function (userDN: string) {
+        return BluebirdPromise.join(
+          HashGenerator.ssha512(newPassword),
+          BluebirdPromise.resolve(userDN));
+      })
+      .then(function (res: string[]) {
+        const change = {
+          operation: "replace",
+          modification: {
+            userPassword: res[0]
+          }
+        };
+        that.logger.debug("Password new='%s'", change.modification.userPassword);
+        return that.connector.modifyAsync(res[1], change);
+      })
+      .then(function () {
+        return that.connector.unbindAsync();
+      });
+  }
+}
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/SessionFactory.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/SessionFactory.ts
new file mode 100644
index 00000000..0b6c4bff
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/SessionFactory.ts
@@ -0,0 +1,37 @@
+import Ldapjs = require("ldapjs");
+import Winston = require("winston");
+
+import { IConnectorFactory } from "./connector/IConnectorFactory";
+import { ISessionFactory } from "./ISessionFactory";
+import { ISession } from "./ISession";
+import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
+import { Session } from "./Session";
+import { SafeSession } from "./SafeSession";
+
+
+export class SessionFactory implements ISessionFactory {
+  private config: LdapConfiguration;
+  private connectorFactory: IConnectorFactory;
+  private logger: typeof Winston;
+
+  constructor(ldapConfiguration: LdapConfiguration,
+    connectorFactory: IConnectorFactory,
+    logger: typeof Winston) {
+    this.config = ldapConfiguration;
+    this.connectorFactory = connectorFactory;
+    this.logger = logger;
+  }
+
+  create(userDN: string, password: string): ISession {
+    const connector = this.connectorFactory.create();
+    return new SafeSession(
+      new Session(
+        userDN,
+        password,
+        this.config,
+        connector,
+        this.logger
+      )
+    );
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/SessionFactoryStub.spec.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/SessionFactoryStub.spec.ts
new file mode 100644
index 00000000..face3930
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/SessionFactoryStub.spec.ts
@@ -0,0 +1,16 @@
+import Sinon = require("sinon");
+
+import { ISession } from "./ISession";
+import { ISessionFactory } from "./ISessionFactory";
+
+export class SessionFactoryStub implements ISessionFactory {
+  createStub: Sinon.SinonStub;
+
+  constructor() {
+    this.createStub = Sinon.stub();
+  }
+
+  create(userDN: string, password: string): ISession {
+    return this.createStub(userDN, password);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/SessionStub.spec.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/SessionStub.spec.ts
new file mode 100644
index 00000000..5faf2ba1
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/SessionStub.spec.ts
@@ -0,0 +1,46 @@
+import Bluebird = require("bluebird");
+import Sinon = require("sinon");
+
+import { ISession } from "./ISession";
+
+export class SessionStub implements ISession {
+  openStub: Sinon.SinonStub;
+  closeStub: Sinon.SinonStub;
+  searchUserDnStub: Sinon.SinonStub;
+  searchEmailsStub: Sinon.SinonStub;
+  searchGroupsStub: Sinon.SinonStub;
+  modifyPasswordStub: Sinon.SinonStub;
+
+  constructor() {
+    this.openStub = Sinon.stub();
+    this.closeStub = Sinon.stub();
+    this.searchUserDnStub = Sinon.stub();
+    this.searchEmailsStub = Sinon.stub();
+    this.searchGroupsStub = Sinon.stub();
+    this.modifyPasswordStub = Sinon.stub();
+  }
+
+  open(): Bluebird<void> {
+    return this.openStub();
+  }
+
+  close(): Bluebird<void> {
+    return this.closeStub();
+  }
+
+  searchUserDn(username: string): Bluebird<string> {
+    return this.searchUserDnStub(username);
+  }
+
+  searchEmails(username: string): Bluebird<string[]> {
+    return this.searchEmailsStub(username);
+  }
+
+  searchGroups(username: string): Bluebird<string[]> {
+    return this.searchGroupsStub(username);
+  }
+
+  modifyPassword(username: string, newPassword: string): Bluebird<void> {
+    return this.modifyPasswordStub(username, newPassword);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/connector/Connector.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/Connector.ts
new file mode 100644
index 00000000..2542ea7f
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/Connector.ts
@@ -0,0 +1,69 @@
+import LdapJs = require("ldapjs");
+import EventEmitter = require("events");
+import Bluebird = require("bluebird");
+import { IConnector } from "./IConnector";
+import Exceptions = require("../../../../Exceptions");
+
+interface SearchEntry {
+  object: any;
+}
+
+export interface ClientAsync {
+  on(event: string, callback: (data?: any) => void): void;
+  bindAsync(username: string, password: string): Bluebird<void>;
+  unbindAsync(): Bluebird<void>;
+  searchAsync(base: string, query: LdapJs.SearchOptions): Bluebird<EventEmitter>;
+  modifyAsync(userdn: string, change: LdapJs.Change): Bluebird<void>;
+}
+
+export class Connector implements IConnector {
+  private client: ClientAsync;
+
+  constructor(url: string, ldapjs: typeof LdapJs) {
+    const ldapClient = ldapjs.createClient({
+      url: url,
+      reconnect: true
+    });
+
+    /*const clientLogger = (ldapClient as any).log;
+    if (clientLogger) {
+      clientLogger.level("trace");
+    }*/
+
+    this.client = Bluebird.promisifyAll(ldapClient) as any;
+  }
+
+  bindAsync(username: string, password: string): Bluebird<void> {
+    return this.client.bindAsync(username, password);
+  }
+
+  unbindAsync(): Bluebird<void> {
+    return this.client.unbindAsync();
+  }
+
+  searchAsync(base: string, query: any): Bluebird<any[]> {
+    const that = this;
+    return this.client.searchAsync(base, query)
+      .then(function (res: EventEmitter) {
+        const doc: SearchEntry[] = [];
+        return new Bluebird<any[]>((resolve, reject) => {
+          res.on("searchEntry", function (entry: SearchEntry) {
+            doc.push(entry.object);
+          });
+          res.on("error", function (err: Error) {
+            reject(new Exceptions.LdapSearchError(err.message));
+          });
+          res.on("end", function () {
+            resolve(doc);
+          });
+        });
+      })
+      .catch(function (err: Error) {
+        return Bluebird.reject(new Exceptions.LdapSearchError(err.message));
+      });
+  }
+
+  modifyAsync(dn: string, changeRequest: any): Bluebird<void> {
+    return this.client.modifyAsync(dn, changeRequest);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorFactory.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorFactory.ts
new file mode 100644
index 00000000..61fef07a
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorFactory.ts
@@ -0,0 +1,18 @@
+import { IConnector } from "./IConnector";
+import { Connector } from "./Connector";
+import { LdapConfiguration } from "../../../../configuration/schema/LdapConfiguration";
+import { Ldapjs } from "Dependencies";
+
+export class ConnectorFactory {
+  private configuration: LdapConfiguration;
+  private ldapjs: Ldapjs;
+
+  constructor(configuration: LdapConfiguration, ldapjs: Ldapjs) {
+    this.configuration = configuration;
+    this.ldapjs = ldapjs;
+  }
+
+  create(): IConnector {
+    return new Connector(this.configuration.url, this.ldapjs);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorFactoryStub.spec.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorFactoryStub.spec.ts
new file mode 100644
index 00000000..d11fa638
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorFactoryStub.spec.ts
@@ -0,0 +1,17 @@
+import BluebirdPromise = require("bluebird");
+import Sinon = require("sinon");
+
+import { IConnectorFactory } from "./IConnectorFactory";
+import { IConnector } from "./IConnector";
+
+export class ConnectorFactoryStub implements IConnectorFactory {
+  createStub: Sinon.SinonStub;
+
+  constructor() {
+    this.createStub = Sinon.stub();
+  }
+
+  create(): IConnector {
+    return this.createStub();
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorStub.spec.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorStub.spec.ts
new file mode 100644
index 00000000..0b78225b
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/ConnectorStub.spec.ts
@@ -0,0 +1,34 @@
+import BluebirdPromise = require("bluebird");
+import Sinon = require("sinon");
+
+import { IConnector } from "./IConnector";
+
+export class ConnectorStub implements IConnector {
+  bindAsyncStub: Sinon.SinonStub;
+  unbindAsyncStub: Sinon.SinonStub;
+  searchAsyncStub: Sinon.SinonStub;
+  modifyAsyncStub: Sinon.SinonStub;
+
+  constructor() {
+    this.bindAsyncStub = Sinon.stub();
+    this.unbindAsyncStub = Sinon.stub();
+    this.searchAsyncStub = Sinon.stub();
+    this.modifyAsyncStub = Sinon.stub();
+  }
+
+  bindAsync(username: string, password: string): BluebirdPromise<void> {
+    return this.bindAsyncStub(username, password);
+  }
+
+  unbindAsync(): BluebirdPromise<void> {
+    return this.unbindAsyncStub();
+  }
+
+  searchAsync(base: string, query: any): BluebirdPromise<any[]> {
+    return this.searchAsyncStub(base, query);
+  }
+
+  modifyAsync(dn: string, changeRequest: any): BluebirdPromise<void> {
+    return this.modifyAsyncStub(dn, changeRequest);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/connector/IConnector.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/IConnector.ts
new file mode 100644
index 00000000..1e63ab19
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/IConnector.ts
@@ -0,0 +1,9 @@
+import Bluebird = require("bluebird");
+import EventEmitter = require("events");
+
+export interface IConnector {
+  bindAsync(username: string, password: string): Bluebird<void>;
+  unbindAsync(): Bluebird<void>;
+  searchAsync(base: string, query: any): Bluebird<any[]>;
+  modifyAsync(dn: string, changeRequest: any): Bluebird<void>;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/backends/ldap/connector/IConnectorFactory.ts b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/IConnectorFactory.ts
new file mode 100644
index 00000000..f9ed65ef
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/backends/ldap/connector/IConnectorFactory.ts
@@ -0,0 +1,5 @@
+import { IConnector } from "./IConnector";
+
+export interface IConnectorFactory {
+  create(): IConnector;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/totp/ITotpHandler.ts b/themes/triangles/server/src/lib/authentication/totp/ITotpHandler.ts
new file mode 100644
index 00000000..d600d31e
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/totp/ITotpHandler.ts
@@ -0,0 +1,6 @@
+import { TOTPSecret } from "../../../../types/TOTPSecret";
+
+export interface ITotpHandler {
+  generate(label: string, issuer: string): TOTPSecret;
+  validate(token: string, secret: string): boolean;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/totp/TotpHandler.spec.ts b/themes/triangles/server/src/lib/authentication/totp/TotpHandler.spec.ts
new file mode 100644
index 00000000..67cffa63
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/totp/TotpHandler.spec.ts
@@ -0,0 +1,39 @@
+
+import { TotpHandler } from "./TotpHandler";
+import Sinon = require("sinon");
+import Speakeasy = require("speakeasy");
+import Assert = require("assert");
+
+describe("authentication/totp/TotpHandler", function() {
+  let totpValidator: TotpHandler;
+  let validateStub: Sinon.SinonStub;
+
+  beforeEach(() => {
+    validateStub = Sinon.stub(Speakeasy.totp, "verify");
+    totpValidator = new TotpHandler(Speakeasy);
+  });
+
+  afterEach(function() {
+    validateStub.restore();
+  });
+
+  it("should validate the TOTP token", function() {
+    const totp_secret = "NBD2ZV64R9UV1O7K";
+    const token = "token";
+    validateStub.withArgs({
+      secret: totp_secret,
+      token: token,
+      encoding: "base32",
+      window: 1
+    }).returns(true);
+    Assert(totpValidator.validate(token, totp_secret));
+  });
+
+  it("should not validate a wrong TOTP token", function() {
+    const totp_secret = "NBD2ZV64R9UV1O7K";
+    const token = "wrong token";
+    validateStub.returns(false);
+    Assert(!totpValidator.validate(token, totp_secret));
+  });
+});
+
diff --git a/themes/triangles/server/src/lib/authentication/totp/TotpHandler.ts b/themes/triangles/server/src/lib/authentication/totp/TotpHandler.ts
new file mode 100644
index 00000000..dfab502a
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/totp/TotpHandler.ts
@@ -0,0 +1,36 @@
+import { ITotpHandler } from "./ITotpHandler";
+import { TOTPSecret } from "../../../../types/TOTPSecret";
+import Speakeasy = require("speakeasy");
+
+const TOTP_ENCODING = "base32";
+const WINDOW: number = 1;
+
+export class TotpHandler implements ITotpHandler {
+  private speakeasy: typeof Speakeasy;
+
+  constructor(speakeasy: typeof Speakeasy) {
+    this.speakeasy = speakeasy;
+  }
+
+  generate(label: string, issuer: string): TOTPSecret {
+    const secret = this.speakeasy.generateSecret({
+      otpauth_url: false
+    }) as TOTPSecret;
+
+    secret.otpauth_url = this.speakeasy.otpauthURL({
+      secret: secret.ascii,
+      label: label,
+      issuer: issuer
+    });
+    return secret;
+  }
+
+  validate(token: string, secret: string): boolean {
+    return this.speakeasy.totp.verify({
+      secret: secret,
+      encoding: TOTP_ENCODING,
+      token: token,
+      window: WINDOW
+    });
+  }
+}
diff --git a/themes/triangles/server/src/lib/authentication/totp/TotpHandlerStub.spec.ts b/themes/triangles/server/src/lib/authentication/totp/TotpHandlerStub.spec.ts
new file mode 100644
index 00000000..ea93330d
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/totp/TotpHandlerStub.spec.ts
@@ -0,0 +1,22 @@
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import { ITotpHandler } from "./ITotpHandler";
+import { TOTPSecret } from "../../../../types/TOTPSecret";
+
+export class TotpHandlerStub implements ITotpHandler {
+  generateStub: Sinon.SinonStub;
+  validateStub: Sinon.SinonStub;
+
+  constructor() {
+    this.generateStub = Sinon.stub();
+    this.validateStub = Sinon.stub();
+  }
+
+  generate(label: string, issuer: string): TOTPSecret {
+    return this.generateStub(label, issuer);
+  }
+
+  validate(token: string, secret: string): boolean {
+    return this.validateStub(token, secret);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/u2f/IU2fHandler.ts b/themes/triangles/server/src/lib/authentication/u2f/IU2fHandler.ts
new file mode 100644
index 00000000..b9b7d6f2
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/u2f/IU2fHandler.ts
@@ -0,0 +1,9 @@
+import U2f = require("u2f");
+
+export interface IU2fHandler {
+  request(appId: string, keyHandle?: string): U2f.Request;
+  checkRegistration(registrationRequest: U2f.Request, registrationResponse: U2f.RegistrationData)
+    : U2f.RegistrationResult | U2f.Error;
+  checkSignature(signatureRequest: U2f.Request, signatureResponse: U2f.SignatureData, publicKey: string)
+    : U2f.SignatureResult | U2f.Error;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authentication/u2f/U2fHandler.ts b/themes/triangles/server/src/lib/authentication/u2f/U2fHandler.ts
new file mode 100644
index 00000000..bf3891e5
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/u2f/U2fHandler.ts
@@ -0,0 +1,24 @@
+import { IU2fHandler } from "./IU2fHandler";
+import U2f = require("u2f");
+
+export class U2fHandler implements IU2fHandler {
+  private u2f: typeof U2f;
+
+  constructor(u2f: typeof U2f) {
+    this.u2f = u2f;
+  }
+
+  request(appId: string, keyHandle?: string): U2f.Request {
+    return this.u2f.request(appId, keyHandle);
+  }
+
+  checkRegistration(registrationRequest: U2f.Request, registrationResponse: U2f.RegistrationData)
+    : U2f.RegistrationResult | U2f.Error {
+    return this.u2f.checkRegistration(registrationRequest, registrationResponse);
+  }
+
+  checkSignature(signatureRequest: U2f.Request, signatureResponse: U2f.SignatureData, publicKey: string)
+    : U2f.SignatureResult | U2f.Error {
+    return this.u2f.checkSignature(signatureRequest, signatureResponse, publicKey);
+  }
+}
diff --git a/themes/triangles/server/src/lib/authentication/u2f/U2fHandlerStub.spec.ts b/themes/triangles/server/src/lib/authentication/u2f/U2fHandlerStub.spec.ts
new file mode 100644
index 00000000..135d7eb0
--- /dev/null
+++ b/themes/triangles/server/src/lib/authentication/u2f/U2fHandlerStub.spec.ts
@@ -0,0 +1,31 @@
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import U2f = require("u2f");
+import { IU2fHandler } from "./IU2fHandler";
+
+
+export class U2fHandlerStub implements IU2fHandler {
+  requestStub: Sinon.SinonStub;
+  checkRegistrationStub: Sinon.SinonStub;
+  checkSignatureStub: Sinon.SinonStub;
+
+  constructor() {
+    this.requestStub = Sinon.stub();
+    this.checkRegistrationStub = Sinon.stub();
+    this.checkSignatureStub = Sinon.stub();
+  }
+
+  request(appId: string, keyHandle?: string): U2f.Request {
+    return this.requestStub(appId, keyHandle);
+  }
+
+  checkRegistration(registrationRequest: U2f.Request, registrationResponse: U2f.RegistrationData)
+    : U2f.RegistrationResult | U2f.Error {
+    return this.checkRegistrationStub(registrationRequest, registrationResponse);
+  }
+
+  checkSignature(signatureRequest: U2f.Request, signatureResponse: U2f.SignatureData, publicKey: string)
+    : U2f.SignatureResult | U2f.Error {
+    return this.checkSignatureStub(signatureRequest, signatureResponse, publicKey);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authorization/Authorizer.spec.ts b/themes/triangles/server/src/lib/authorization/Authorizer.spec.ts
new file mode 100644
index 00000000..58681404
--- /dev/null
+++ b/themes/triangles/server/src/lib/authorization/Authorizer.spec.ts
@@ -0,0 +1,372 @@
+
+import Assert = require("assert");
+import winston = require("winston");
+import { Authorizer } from "./Authorizer";
+import { ACLConfiguration, ACLRule } from "../configuration/schema/AclConfiguration";
+import { Level } from "./Level";
+
+describe("authorization/Authorizer", function () {
+  let authorizer: Authorizer;
+  let configuration: ACLConfiguration;
+
+  describe("configuration is null", function() {
+    it("should allow access to anything, anywhere for anybody", function() {
+      configuration = undefined;
+      authorizer = new Authorizer(configuration, winston);
+
+      Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1", "group2"]}), Level.BYPASS);
+      Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc"}, {user: "user1", groups: ["group1", "group2"]}), Level.BYPASS);
+      Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user2", groups: ["group1", "group2"]}), Level.BYPASS);
+      Assert.equal(authorizer.authorization({domain: "admin.example.com", resource: "/"}, {user: "user3", groups: ["group3"]}), Level.BYPASS);
+    });
+  });
+
+  describe("configuration is not null", function () {
+    beforeEach(function () {
+      configuration = {
+        default_policy: "deny",
+        rules: []
+      };
+      authorizer = new Authorizer(configuration, winston);
+    });
+
+    describe("check access control with default policy to deny", function () {
+      beforeEach(function () {
+        configuration.default_policy = "deny";
+      });
+
+      it("should deny access when no rule is provided", function () {
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+      });
+
+      it("should control access when multiple domain matcher is provided", function () {
+        configuration.rules = [{
+          domain: "*.mail.example.com",
+          policy: "two_factor",
+          subject: "user:user1",
+          resources: [".*"]
+        }];
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "mx1.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "mx1.server.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+      });
+
+      it("should allow access to all resources when resources is not provided", function () {
+        configuration.rules = [{
+          domain: "*.mail.example.com",
+          policy: "two_factor",
+          subject: "user:user1"
+        }];
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "mx1.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "mx1.server.mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "mail.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+      });
+
+      describe("check user rules", function () {
+        it("should allow access when user has a matching allowing rule", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: [".*"],
+            subject: "user:user1"
+          }];
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/another/resource"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "another.home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        });
+
+        it("should deny to other users", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: [".*"],
+            subject: "user:user1"
+          }];
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user2", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/another/resource"}, {user: "user2", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "another.home.example.com", resource: "/"}, {user: "user2", groups: ["group1"]}), Level.DENY);
+        });
+
+        it("should allow user access only to specific resources", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: ["/private/.*", "^/begin", "/end$"],
+            subject: "user:user1"
+          }];
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/class"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/middle/private/class"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/begin"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/not/begin"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc/end"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/abc/end/x"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        });
+
+        it("should allow access to multiple domains", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: [".*"],
+            subject: "user:user1"
+          }, {
+            domain: "home1.example.com",
+            policy: "one_factor",
+            resources: [".*"],
+            subject: "user:user1"
+          }, {
+            domain: "home2.example.com",
+            policy: "deny",
+            resources: [".*"],
+            subject: "user:user1"
+          }];
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home1.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.ONE_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home2.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "home3.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        });
+
+        it("should apply rules in order", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "one_factor",
+            resources: ["/my/private/resource"],
+            subject: "user:user1"
+          }, {
+            domain: "home.example.com",
+            policy: "deny",
+            resources: ["^/my/private/.*"],
+            subject: "user:user1"
+          }, {
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: ["^/my/.*"],
+            subject: "user:user1"
+          }];
+
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/poney"}, {user: "user1", groups: ["group1"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/private/duck"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/my/private/resource"}, {user: "user1", groups: ["group1"]}), Level.ONE_FACTOR);
+        });
+      });
+
+      describe("check group rules", function () {
+        it("should allow access when user is in group having a matching allowing rule", function () {
+          configuration.rules = [{
+            domain: "home.example.com",
+            policy: "two_factor",
+            resources: ["^/$"],
+            subject: "group:group1"
+          }, {
+            domain: "home.example.com",
+            policy: "one_factor",
+            resources: ["^/test$"],
+            subject: "group:group2"
+          }, {
+            domain: "home.example.com",
+            policy: "deny",
+            resources: ["^/private$"],
+            subject: "group:group2"
+          }];
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"},
+            {user: "user1", groups: ["group1", "group2", "group3"]}), Level.TWO_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/test"},
+            {user: "user1", groups: ["group1", "group2", "group3"]}), Level.ONE_FACTOR);
+          Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"},
+            {user: "user1", groups: ["group1", "group2", "group3"]}), Level.DENY);
+          Assert.equal(authorizer.authorization({domain: "another.home.example.com", resource: "/"},
+            {user: "user1", groups: ["group1", "group2", "group3"]}), Level.DENY);
+        });
+      });
+    });
+
+    describe("check any rules", function () {
+      it("should control access when any rules are defined", function () {
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "bypass",
+          resources: ["^/public$"]
+        }, {
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["^/private$"]
+        }];
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"},
+          {user: "user1", groups: ["group1", "group2", "group3"]}), Level.BYPASS);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"},
+          {user: "user1", groups: ["group1", "group2", "group3"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"},
+          {user: "user4", groups: ["group5"]}), Level.BYPASS);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private"},
+          {user: "user4", groups: ["group5"]}), Level.DENY);
+      });
+    });
+
+    describe("check access control with default policy to allow", function () {
+      beforeEach(function () {
+        configuration.default_policy = "bypass";
+      });
+
+      it("should allow access to anything when no rule is provided", function () {
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/test"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
+      });
+
+      it("should deny access to one resource when defined", function () {
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["/test"],
+          subject: "user:user1"
+        }];
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/test"}, {user: "user1", groups: ["group1"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "user1", groups: ["group1"]}), Level.BYPASS);
+      });
+    });
+
+    describe("check access control with complete use case", function () {
+      beforeEach(function () {
+        configuration.default_policy = "deny";
+      });
+
+      it("should control access of multiple user (real use case)", function () {
+        // Let say we have three users: admin, john, harry.
+        // admin is in groups ["admins"]
+        // john is in groups ["dev", "admin-private"]
+        // harry is in groups ["dev"]
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/public$", "^/$"]
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: [".*"],
+          subject: "group:admins"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/private/?.*"],
+          subject: "group:admin-private"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/private/john$"],
+          subject: "user:john"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/private/harry"],
+          subject: "user:harry"
+        }];
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "admin", groups: ["admins"]}), Level.TWO_FACTOR);
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "john", groups: ["dev", "admin-private"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev", "admin-private"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "john", groups: ["dev", "admin-private"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "john", groups: ["dev", "admin-private"]}), Level.TWO_FACTOR);
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/"}, {user: "harry", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/public"}, {user: "harry", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev"}, {user: "harry", groups: ["dev"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "harry", groups: ["dev"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/admin"}, {user: "harry", groups: ["dev"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/josh"}, {user: "harry", groups: ["dev"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/john"}, {user: "harry", groups: ["dev"]}), Level.DENY);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/private/harry"}, {user: "harry", groups: ["dev"]}), Level.TWO_FACTOR);
+      });
+
+      it("should allow when allowed at group level and denied at user level", function () {
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["^/dev/bob$"],
+          subject: "user:john"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/dev/?.*$"],
+          subject: "group:dev"
+        }];
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.DENY);
+      });
+
+      it("should allow access when allowed at 'any' level and denied at user level", function () {
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["^/dev/bob$"],
+          subject: "user:john"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/dev/?.*$"]
+        }];
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.DENY);
+      });
+
+      it("should allow access when allowed at 'any' level and denied at group level", function () {
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["^/dev/bob$"],
+          subject: "group:dev"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/dev/?.*$"]
+        }];
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.DENY);
+      });
+
+      it("should respect rules precedence", function () {
+        // the priority from least to most is 'default_policy', 'all', 'group', 'user'
+        // and the first rules in each category as a lower priority than the latest.
+        // You can think of it that way: they override themselves inside each category.
+        configuration.rules = [{
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/dev/?.*$"],
+          subject: "user:john"
+        }, {
+          domain: "home.example.com",
+          policy: "deny",
+          resources: ["^/dev/bob$"],
+          subject: "group:dev"
+        }, {
+          domain: "home.example.com",
+          policy: "two_factor",
+          resources: ["^/dev/?.*$"]
+        }];
+
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/john"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
+        Assert.equal(authorizer.authorization({domain: "home.example.com", resource: "/dev/bob"}, {user: "john", groups: ["dev"]}), Level.TWO_FACTOR);
+      });
+    });
+  });
+});
diff --git a/themes/triangles/server/src/lib/authorization/Authorizer.ts b/themes/triangles/server/src/lib/authorization/Authorizer.ts
new file mode 100644
index 00000000..889b7ec2
--- /dev/null
+++ b/themes/triangles/server/src/lib/authorization/Authorizer.ts
@@ -0,0 +1,85 @@
+
+import { ACLConfiguration, ACLPolicy, ACLRule } from "../configuration/schema/AclConfiguration";
+import { IAuthorizer } from "./IAuthorizer";
+import { Winston } from "../../../types/Dependencies";
+import { MultipleDomainMatcher } from "./MultipleDomainMatcher";
+import { Level } from "./Level";
+import { Object } from "./Object";
+import { Subject } from "./Subject";
+
+function MatchDomain(actualDomain: string) {
+  return function (rule: ACLRule): boolean {
+    return MultipleDomainMatcher.match(actualDomain, rule.domain);
+  };
+}
+
+function MatchResource(actualResource: string) {
+  return function (rule: ACLRule): boolean {
+    // If resources key is not provided, the rule applies to all resources.
+    if (!rule.resources) return true;
+
+    for (let i = 0; i < rule.resources.length; ++i) {
+      const regexp = new RegExp(rule.resources[i]);
+      if (regexp.test(actualResource)) return true;
+    }
+    return false;
+  };
+}
+
+function MatchSubject(subject: Subject) {
+  return (rule: ACLRule) => {
+    // If no subject, matches anybody
+    if (!rule.subject) return true;
+
+    if (rule.subject.startsWith("user:")) {
+      const ruleUser = rule.subject.split(":")[1];
+      if (subject.user == ruleUser) return true;
+    }
+
+    if (rule.subject.startsWith("group:")) {
+      const ruleGroup = rule.subject.split(":")[1];
+      if (subject.groups.indexOf(ruleGroup) > -1) return true;
+    }
+    return false;
+  };
+}
+
+export class Authorizer implements IAuthorizer {
+  private logger: Winston;
+  private readonly configuration: ACLConfiguration;
+
+  constructor(configuration: ACLConfiguration, logger_: Winston) {
+    this.logger = logger_;
+    this.configuration = configuration;
+  }
+
+  private getMatchingRules(object: Object, subject: Subject): ACLRule[] {
+    const rules = this.configuration.rules;
+    if (!rules) return [];
+    return rules
+      .filter(MatchDomain(object.domain))
+      .filter(MatchResource(object.resource))
+      .filter(MatchSubject(subject));
+  }
+
+  private ruleToLevel(policy: string): Level {
+    if (policy == "bypass") {
+      return Level.BYPASS;
+    } else if (policy == "one_factor") {
+      return Level.ONE_FACTOR;
+    } else if (policy == "two_factor") {
+      return Level.TWO_FACTOR;
+    }
+    return Level.DENY;
+  }
+
+  authorization(object: Object, subject: Subject): Level {
+    if (!this.configuration) return Level.BYPASS;
+
+    const rules = this.getMatchingRules(object, subject);
+
+    return (rules.length > 0)
+      ? this.ruleToLevel(rules[0].policy) // extract the policy of the first matching rule
+      : this.ruleToLevel(this.configuration.default_policy); // otherwise use the default policy
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authorization/AuthorizerStub.spec.ts b/themes/triangles/server/src/lib/authorization/AuthorizerStub.spec.ts
new file mode 100644
index 00000000..9bd6f4a8
--- /dev/null
+++ b/themes/triangles/server/src/lib/authorization/AuthorizerStub.spec.ts
@@ -0,0 +1,17 @@
+import Sinon = require("sinon");
+import { IAuthorizer } from "./IAuthorizer";
+import { Level } from "./Level";
+import { Object } from "./Object";
+import { Subject } from "./Subject";
+
+export class AuthorizerStub implements IAuthorizer {
+  authorizationMock: Sinon.SinonStub;
+
+  constructor() {
+    this.authorizationMock = Sinon.stub();
+  }
+
+  authorization(object: Object, subject: Subject): Level {
+    return this.authorizationMock(object, subject);
+  }
+}
diff --git a/themes/triangles/server/src/lib/authorization/IAuthorizer.ts b/themes/triangles/server/src/lib/authorization/IAuthorizer.ts
new file mode 100644
index 00000000..fe7ba367
--- /dev/null
+++ b/themes/triangles/server/src/lib/authorization/IAuthorizer.ts
@@ -0,0 +1,7 @@
+import { Level } from "./Level";
+import { Subject } from "./Subject";
+import { Object } from "./Object";
+
+export interface IAuthorizer {
+  authorization(object: Object, subject: Subject): Level;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authorization/Level.ts b/themes/triangles/server/src/lib/authorization/Level.ts
new file mode 100644
index 00000000..d1280261
--- /dev/null
+++ b/themes/triangles/server/src/lib/authorization/Level.ts
@@ -0,0 +1,6 @@
+export enum Level {
+  BYPASS = 0,
+  ONE_FACTOR = 1,
+  TWO_FACTOR = 2,
+  DENY = 3
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authorization/MultipleDomainMatcher.ts b/themes/triangles/server/src/lib/authorization/MultipleDomainMatcher.ts
new file mode 100644
index 00000000..64c647a4
--- /dev/null
+++ b/themes/triangles/server/src/lib/authorization/MultipleDomainMatcher.ts
@@ -0,0 +1,12 @@
+
+export class MultipleDomainMatcher {
+  static match(domain: string, pattern: string): boolean {
+    if (pattern.startsWith("*") &&
+      domain.endsWith(pattern.substr(1))) {
+      return true;
+    }
+    else if (domain == pattern) {
+      return true;
+    }
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authorization/Object.ts b/themes/triangles/server/src/lib/authorization/Object.ts
new file mode 100644
index 00000000..5411b0d2
--- /dev/null
+++ b/themes/triangles/server/src/lib/authorization/Object.ts
@@ -0,0 +1,5 @@
+
+export interface Object {
+  domain: string;
+  resource: string;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/authorization/Subject.ts b/themes/triangles/server/src/lib/authorization/Subject.ts
new file mode 100644
index 00000000..310d6b4c
--- /dev/null
+++ b/themes/triangles/server/src/lib/authorization/Subject.ts
@@ -0,0 +1,5 @@
+
+export interface Subject {
+  user: string;
+  groups: string[];
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/ConfigurationParser.spec.ts b/themes/triangles/server/src/lib/configuration/ConfigurationParser.spec.ts
new file mode 100644
index 00000000..60c0f618
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/ConfigurationParser.spec.ts
@@ -0,0 +1,171 @@
+import * as Assert from "assert";
+import { Configuration } from "./schema/Configuration";
+import { ACLConfiguration } from "./schema/AclConfiguration";
+import { ConfigurationParser } from "./ConfigurationParser";
+
+describe("configuration/ConfigurationParser", function () {
+  function buildYamlConfig(): Configuration {
+    const yaml_config: Configuration = {
+      port: 8080,
+      authentication_backend: {
+        ldap: {
+          url: "http://ldap",
+          base_dn: "dc=example,dc=com",
+          additional_users_dn: "ou=users",
+          additional_groups_dn: "ou=groups",
+          user: "user",
+          password: "pass"
+        },
+      },
+      session: {
+        domain: "example.com",
+        secret: "secret",
+        expiration: 40000
+      },
+      storage: {
+        local: {
+          path: "/mydirectory"
+        }
+      },
+      regulation: {
+        max_retries: 3,
+        find_time: 5 * 60,
+        ban_time: 5 * 60
+      },
+      logs_level: "debug",
+      notifier: {
+        email: {
+          username: "user",
+          password: "password",
+          sender: "admin@example.com",
+          service: "gmail"
+        }
+      }
+    };
+    return yaml_config;
+  }
+
+  describe("port", function () {
+    it("should read the port from the yaml file", function () {
+      const yaml_config = buildYamlConfig();
+      yaml_config.port = 7070;
+      const config = ConfigurationParser.parse(yaml_config);
+      Assert.equal(config.port, 7070);
+    });
+
+    it("should default the port to 8080 if not provided", function () {
+      const yaml_config = buildYamlConfig();
+      delete yaml_config.port;
+      const config = ConfigurationParser.parse(yaml_config);
+      Assert.equal(config.port, 8080);
+    });
+  });
+
+  describe("test session configuration", function() {
+    it("should get the session attributes", function () {
+      const yaml_config = buildYamlConfig();
+      yaml_config.session = {
+        domain: "example.com",
+        secret: "secret",
+        expiration: 3600,
+        inactivity: 4000
+      };
+      const config = ConfigurationParser.parse(yaml_config);
+      Assert.equal(config.session.domain, "example.com");
+      Assert.equal(config.session.secret, "secret");
+      Assert.equal(config.session.expiration, 3600);
+      Assert.equal(config.session.inactivity, 4000);
+    });
+
+    it("should be ok not specifying inactivity", function () {
+      const yaml_config = buildYamlConfig();
+      yaml_config.session = {
+        domain: "example.com",
+        secret: "secret",
+        expiration: 3600
+      };
+      const config = ConfigurationParser.parse(yaml_config);
+      Assert.equal(config.session.domain, "example.com");
+      Assert.equal(config.session.secret, "secret");
+      Assert.equal(config.session.expiration, 3600);
+      Assert.equal(config.session.inactivity, undefined);
+    });
+  });
+
+  it("should get the log level", function () {
+    const yaml_config = buildYamlConfig();
+    yaml_config.logs_level = "debug";
+    const config = ConfigurationParser.parse(yaml_config);
+    Assert.equal(config.logs_level, "debug");
+  });
+
+  it("should get the notifier config", function () {
+    const userConfig = buildYamlConfig();
+    userConfig.notifier = {
+      email: {
+        username: "user",
+        password: "pass",
+        sender: "admin@example.com",
+        service: "gmail"
+      }
+    };
+    const config = ConfigurationParser.parse(userConfig);
+    Assert.deepEqual(config.notifier, {
+      email: {
+        username: "user",
+        password: "pass",
+        sender: "admin@example.com",
+        service: "gmail"
+      }
+    });
+  });
+
+  describe("access_control", function() {
+    it("should adapt access_control when it is already ok", function () {
+      const userConfig = buildYamlConfig();
+      userConfig.access_control = {
+        default_policy: "deny",
+        rules: [{
+          domain: "www.example.com",
+          policy: "two_factor",
+          subject: "user:user"
+        }, {
+          domain: "public.example.com",
+          policy: "two_factor"
+        }]
+      };
+      const config = ConfigurationParser.parse(userConfig);
+      Assert.deepEqual(config.access_control, {
+        default_policy: "deny",
+        rules: [{
+          domain: "www.example.com",
+          policy: "two_factor",
+          subject: "user:user"
+        }, {
+          domain: "public.example.com",
+          policy: "two_factor"
+        }]
+      } as ACLConfiguration);
+    });
+
+
+    it("should adapt access_control when it is empty", function () {
+      const userConfig = buildYamlConfig();
+      userConfig.access_control = {} as any;
+      const config = ConfigurationParser.parse(userConfig);
+      Assert.deepEqual(config.access_control, {
+        default_policy: "bypass",
+        rules: []
+      });
+    });
+  });
+
+  describe("default_redirection_url", function() {
+    it("should parse default_redirection_url", function() {
+      const userConfig = buildYamlConfig();
+      userConfig.default_redirection_url = "dummy_url";
+      const config = ConfigurationParser.parse(userConfig);
+      Assert.deepEqual(config.default_redirection_url, "dummy_url");
+    });
+  });
+});
diff --git a/themes/triangles/server/src/lib/configuration/ConfigurationParser.ts b/themes/triangles/server/src/lib/configuration/ConfigurationParser.ts
new file mode 100644
index 00000000..d92d163c
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/ConfigurationParser.ts
@@ -0,0 +1,39 @@
+
+import * as ObjectPath from "object-path";
+import { Configuration, complete } from "./schema/Configuration";
+import Ajv = require("ajv");
+import Path = require("path");
+import Util = require("util");
+
+export class ConfigurationParser {
+  private static parseTypes(configuration: Configuration): string[] {
+    const schema = require(Path.resolve(__dirname, "./Configuration.schema.json"));
+    const ajv = new Ajv({
+      allErrors: true,
+      missingRefs: "fail"
+    });
+    ajv.addMetaSchema(require("ajv/lib/refs/json-schema-draft-06.json"));
+    const valid = ajv.validate(schema, configuration);
+    if (!valid)
+      return ajv.errors.map(
+        (e: Ajv.ErrorObject) => { return ajv.errorsText([e]); });
+    return [];
+  }
+
+  static parse(configuration: Configuration): Configuration {
+    const validationErrors = this.parseTypes(configuration);
+    if (validationErrors.length > 0) {
+      validationErrors.forEach((e: string) => { console.log(e); });
+      throw new Error("Malformed configuration (schema). Please double-check your configuration file.");
+    }
+
+    const [newConfiguration, completionErrors] = complete(configuration);
+
+    if (completionErrors.length > 0) {
+      completionErrors.forEach((e: string) => { console.log(e); });
+      throw new Error("Malformed configuration (validator). Please double-check your configuration file.");
+    }
+    return newConfiguration;
+  }
+}
+
diff --git a/themes/triangles/server/src/lib/configuration/SessionConfigurationBuilder.spec.ts b/themes/triangles/server/src/lib/configuration/SessionConfigurationBuilder.spec.ts
new file mode 100644
index 00000000..d4a3093e
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/SessionConfigurationBuilder.spec.ts
@@ -0,0 +1,149 @@
+import { SessionConfigurationBuilder } from "./SessionConfigurationBuilder";
+import { Configuration } from "./schema/Configuration";
+import { GlobalDependencies } from "../../../types/Dependencies";
+
+import ExpressSession = require("express-session");
+import ConnectRedis = require("connect-redis");
+import Sinon = require("sinon");
+import Assert = require("assert");
+
+describe("configuration/SessionConfigurationBuilder", function () {
+  const configuration: Configuration = {
+    access_control: {
+      default_policy: "deny",
+      rules: []
+    },
+    totp: {
+      issuer: "authelia.com"
+    },
+    authentication_backend: {
+      ldap: {
+        url: "ldap://ldap",
+        user: "user",
+        base_dn: "dc=example,dc=com",
+        password: "password",
+        additional_groups_dn: "ou=groups",
+        additional_users_dn: "ou=users",
+        group_name_attribute: "",
+        groups_filter: "",
+        mail_attribute: "",
+        users_filter: ""
+      },
+    },
+    logs_level: "debug",
+    notifier: {
+      filesystem: {
+        filename: "/test"
+      }
+    },
+    port: 8080,
+    session: {
+      name: "authelia_session",
+      domain: "example.com",
+      expiration: 3600,
+      secret: "secret"
+    },
+    regulation: {
+      max_retries: 3,
+      ban_time: 5 * 60,
+      find_time: 5 * 60
+    },
+    storage: {
+      local: {
+        in_memory: true
+      }
+    }
+  };
+
+  const deps: GlobalDependencies = {
+    ConnectRedis: Sinon.spy() as any,
+    ldapjs: Sinon.spy() as any,
+    nedb: Sinon.spy() as any,
+    session: Sinon.spy() as any,
+    speakeasy: Sinon.spy() as any,
+    u2f: Sinon.spy() as any,
+    winston: Sinon.spy() as any,
+    Redis: Sinon.spy() as any
+  };
+
+  it("should return session options without redis options", function () {
+    const options = SessionConfigurationBuilder.build(configuration, deps);
+    const expectedOptions = {
+      name: "authelia_session",
+      secret: "secret",
+      resave: false,
+      saveUninitialized: true,
+      cookie: {
+        secure: true,
+        httpOnly: true,
+        maxAge: 3600,
+        domain: "example.com"
+      }
+    };
+
+    Assert.deepEqual(expectedOptions, options);
+  });
+
+  it("should return session options with redis options", function () {
+    configuration.session["redis"] = {
+      host: "redis.example.com",
+      port: 6379
+    };
+    const RedisStoreMock = Sinon.spy();
+    const redisClient = Sinon.mock().returns({ on: Sinon.spy() });
+
+    deps.ConnectRedis = Sinon.stub().returns(RedisStoreMock) as any;
+    deps.Redis = {
+      createClient: Sinon.mock().returns(redisClient)
+    } as any;
+
+    const options = SessionConfigurationBuilder.build(configuration, deps);
+
+    const expectedOptions: ExpressSession.SessionOptions = {
+      secret: "secret",
+      resave: false,
+      saveUninitialized: true,
+      name: "authelia_session",
+      cookie: {
+        secure: true,
+        httpOnly: true,
+        maxAge: 3600,
+        domain: "example.com"
+      },
+      store: Sinon.match.object as any
+    };
+
+    Assert((deps.ConnectRedis as Sinon.SinonStub).calledWith(deps.session));
+    Assert.equal(options.secret, expectedOptions.secret);
+    Assert.equal(options.resave, expectedOptions.resave);
+    Assert.equal(options.saveUninitialized, expectedOptions.saveUninitialized);
+    Assert.deepEqual(options.cookie, expectedOptions.cookie);
+    Assert(options.store != undefined);
+  });
+
+  it("should return session options with redis password", function () {
+    configuration.session["redis"] = {
+      host: "redis.example.com",
+      port: 6379,
+      password: "authelia_pass"
+    };
+    const RedisStoreMock = Sinon.spy();
+    const redisClient = Sinon.mock().returns({ on: Sinon.spy() });
+    const createClientStub = Sinon.stub();
+
+    deps.ConnectRedis = Sinon.stub().returns(RedisStoreMock) as any;
+    deps.Redis = {
+      createClient: createClientStub
+    } as any;
+
+    createClientStub.returns(redisClient);
+
+    const options = SessionConfigurationBuilder.build(configuration, deps);
+
+    Assert(createClientStub.calledWith({
+      host: "redis.example.com",
+      port: 6379,
+      password: "authelia_pass"
+    }));
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/SessionConfigurationBuilder.ts b/themes/triangles/server/src/lib/configuration/SessionConfigurationBuilder.ts
new file mode 100644
index 00000000..6ce643d9
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/SessionConfigurationBuilder.ts
@@ -0,0 +1,52 @@
+import ExpressSession = require("express-session");
+import Redis = require("redis");
+
+import { Configuration } from "./schema/Configuration";
+import { GlobalDependencies } from "../../../types/Dependencies";
+import { RedisStoreOptions } from "connect-redis";
+
+export class SessionConfigurationBuilder {
+
+  static build(configuration: Configuration, deps: GlobalDependencies): ExpressSession.SessionOptions {
+    const sessionOptions: ExpressSession.SessionOptions = {
+      name: configuration.session.name,
+      secret: configuration.session.secret,
+      resave: false,
+      saveUninitialized: true,
+      cookie: {
+        secure: true,
+        httpOnly: true,
+        maxAge: configuration.session.expiration,
+        domain: configuration.session.domain
+      },
+    };
+
+    if (configuration.session.redis) {
+      let redisOptions;
+      const options: Redis.ClientOpts = {
+        host: configuration.session.redis.host,
+        port: configuration.session.redis.port
+      };
+
+      if (configuration.session.redis.password) {
+        options["password"] = configuration.session.redis.password;
+      }
+      const client = deps.Redis.createClient(options);
+
+      client.on("error", function (err: Error) {
+        console.error("Redis error:", err);
+      });
+
+      redisOptions = {
+        client: client,
+        logErrors: true
+      };
+
+      if (redisOptions) {
+        const RedisStore = deps.ConnectRedis(deps.session);
+        sessionOptions.store = new RedisStore(redisOptions);
+      }
+    }
+    return sessionOptions;
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/AclConfiguration.spec.ts b/themes/triangles/server/src/lib/configuration/schema/AclConfiguration.spec.ts
new file mode 100644
index 00000000..d1e2a03a
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/AclConfiguration.spec.ts
@@ -0,0 +1,34 @@
+import { ACLConfiguration, complete } from "./AclConfiguration";
+import Assert = require("assert");
+
+describe("configuration/schema/AclConfiguration", function() {
+  it("should complete ACLConfiguration", function() {
+    const configuration: ACLConfiguration = {};
+    const [newConfiguration, errors] = complete(configuration);
+
+    Assert.deepEqual(newConfiguration.default_policy, "bypass");
+    Assert.deepEqual(newConfiguration.rules, []);
+  });
+
+  it("should return errors when subject is not good", function() {
+    const configuration: ACLConfiguration = {
+      default_policy: "deny",
+      rules: [{
+        domain: "dev.example.com",
+        subject: "user:abc",
+        policy: "bypass"
+      }, {
+        domain: "dev.example.com",
+        subject: "user:def",
+        policy: "bypass"
+      }, {
+        domain: "dev.example.com",
+        subject: "badkey:abc",
+        policy: "bypass"
+      }]
+    };
+    const [newConfiguration, errors] = complete(configuration);
+
+    Assert.deepEqual(errors, ["Rule 2 has wrong subject. It should be starting with user: or group:."]);
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/AclConfiguration.ts b/themes/triangles/server/src/lib/configuration/schema/AclConfiguration.ts
new file mode 100644
index 00000000..40401dd6
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/AclConfiguration.ts
@@ -0,0 +1,41 @@
+
+export type ACLPolicy = "deny" | "bypass" | "one_factor" | "two_factor";
+
+export type ACLRule = {
+  domain: string;
+  resources?: string[];
+  subject?: string;
+  policy: ACLPolicy;
+};
+
+export interface ACLConfiguration {
+  default_policy?: ACLPolicy;
+  rules?: ACLRule[];
+}
+
+export function complete(configuration: ACLConfiguration): [ACLConfiguration, string[]] {
+  const newConfiguration: ACLConfiguration = (configuration)
+    ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.default_policy) {
+    newConfiguration.default_policy = "bypass";
+  }
+
+  if (!newConfiguration.rules) {
+    newConfiguration.rules = [];
+  }
+
+  if (newConfiguration.rules.length > 0) {
+    const errors: string[] = [];
+    newConfiguration.rules.forEach((r, idx) => {
+      if (r.subject && !r.subject.match(/^(user|group):[a-zA-Z0-9]+$/)) {
+        errors.push(`Rule ${idx} has wrong subject. It should be starting with user: or group:.`);
+      }
+    });
+    if (errors.length > 0) {
+      return [newConfiguration, errors];
+    }
+  }
+
+  return [newConfiguration, []];
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.spec.ts b/themes/triangles/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.spec.ts
new file mode 100644
index 00000000..3ca86381
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.spec.ts
@@ -0,0 +1,11 @@
+import { AuthenticationBackendConfiguration, complete } from "./AuthenticationBackendConfiguration";
+import Assert = require("assert");
+
+describe("configuration/schema/AuthenticationBackendConfiguration", function() {
+  it("should ensure there is at least one key", function() {
+    const configuration: AuthenticationBackendConfiguration = {} as any;
+    const [newConfiguration, error] = complete(configuration);
+
+    Assert.equal(error, "Authentication backend must have one of the following keys:`ldap` or `file`");
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.ts b/themes/triangles/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.ts
new file mode 100644
index 00000000..7f77f894
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/AuthenticationBackendConfiguration.ts
@@ -0,0 +1,25 @@
+import { LdapConfiguration } from "./LdapConfiguration";
+import { FileUsersDatabaseConfiguration } from "./FileUsersDatabaseConfiguration";
+
+export interface AuthenticationBackendConfiguration {
+  ldap?: LdapConfiguration;
+  file?: FileUsersDatabaseConfiguration;
+}
+
+export function complete(
+  configuration: AuthenticationBackendConfiguration)
+  : [AuthenticationBackendConfiguration, string] {
+
+  const newConfiguration: AuthenticationBackendConfiguration = (configuration)
+    ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (Object.keys(newConfiguration).length != 1) {
+    return [
+      newConfiguration,
+      "Authentication backend must have one of the following keys:" +
+      "`ldap` or `file`"
+    ];
+  }
+
+  return [newConfiguration, undefined];
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/Configuration.ts b/themes/triangles/server/src/lib/configuration/schema/Configuration.ts
new file mode 100644
index 00000000..8d16a5fb
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/Configuration.ts
@@ -0,0 +1,68 @@
+import { ACLConfiguration, complete as AclConfigurationComplete } from "./AclConfiguration";
+import { AuthenticationBackendConfiguration, complete as AuthenticationBackendComplete } from "./AuthenticationBackendConfiguration";
+import { NotifierConfiguration, complete as NotifierConfigurationComplete } from "./NotifierConfiguration";
+import { RegulationConfiguration, complete as RegulationConfigurationComplete } from "./RegulationConfiguration";
+import { SessionConfiguration, complete as SessionConfigurationComplete } from "./SessionConfiguration";
+import { StorageConfiguration, complete as StorageConfigurationComplete } from "./StorageConfiguration";
+import { TotpConfiguration, complete as TotpConfigurationComplete } from "./TotpConfiguration";
+
+export interface Configuration {
+  access_control?: ACLConfiguration;
+  authentication_backend: AuthenticationBackendConfiguration;
+  default_redirection_url?: string;
+  logs_level?: string;
+  notifier?: NotifierConfiguration;
+  port?: number;
+  regulation?: RegulationConfiguration;
+  session?: SessionConfiguration;
+  storage?: StorageConfiguration;
+  totp?: TotpConfiguration;
+}
+
+export function complete(
+  configuration: Configuration):
+  [Configuration, string[]] {
+
+  const newConfiguration: Configuration = JSON.parse(
+    JSON.stringify(configuration));
+  const errors: string[] = [];
+
+  const [acls, aclsErrors] = AclConfigurationComplete(
+    newConfiguration.access_control);
+
+  newConfiguration.access_control = acls;
+  if (aclsErrors.length > 0) {
+    errors.concat(aclsErrors);
+  }
+
+  const [backend, error] =
+    AuthenticationBackendComplete(
+      newConfiguration.authentication_backend);
+
+  if (error) errors.push(error);
+  newConfiguration.authentication_backend = backend;
+
+  if (!newConfiguration.logs_level) {
+    newConfiguration.logs_level = "info";
+  }
+
+  const [notifier, notifierError] = NotifierConfigurationComplete(
+    newConfiguration.notifier);
+  newConfiguration.notifier = notifier;
+  if (notifierError) errors.push(notifierError);
+
+  if (!newConfiguration.port) {
+    newConfiguration.port = 8080;
+  }
+
+  newConfiguration.regulation = RegulationConfigurationComplete(
+    newConfiguration.regulation);
+  newConfiguration.session = SessionConfigurationComplete(
+    newConfiguration.session);
+  newConfiguration.storage = StorageConfigurationComplete(
+    newConfiguration.storage);
+  newConfiguration.totp = TotpConfigurationComplete(
+    newConfiguration.totp);
+
+  return [newConfiguration, errors];
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/FileUsersDatabaseConfiguration.ts b/themes/triangles/server/src/lib/configuration/schema/FileUsersDatabaseConfiguration.ts
new file mode 100644
index 00000000..d19002ba
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/FileUsersDatabaseConfiguration.ts
@@ -0,0 +1,4 @@
+
+export interface FileUsersDatabaseConfiguration {
+  path: string;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/LdapConfiguration.spec.ts b/themes/triangles/server/src/lib/configuration/schema/LdapConfiguration.spec.ts
new file mode 100644
index 00000000..cc73d108
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/LdapConfiguration.spec.ts
@@ -0,0 +1,25 @@
+import Assert = require("assert");
+import { LdapConfiguration, complete } from "./LdapConfiguration";
+
+describe("configuration/schema/AuthenticationMethodsConfiguration", function() {
+  it("should ensure at least one key is provided", function() {
+    const configuration: LdapConfiguration = {
+      url: "ldap.example.com",
+      base_dn: "dc=example,dc=com",
+      user: "admin",
+      password: "password"
+    };
+    const newConfiguration = complete(configuration);
+
+    Assert.deepEqual(newConfiguration, {
+      url: "ldap.example.com",
+      base_dn: "dc=example,dc=com",
+      user: "admin",
+      password: "password",
+      users_filter: "cn={0}",
+      group_name_attribute: "cn",
+      groups_filter: "member={dn}",
+      mail_attribute: "mail"
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/LdapConfiguration.ts b/themes/triangles/server/src/lib/configuration/schema/LdapConfiguration.ts
new file mode 100644
index 00000000..5dacb939
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/LdapConfiguration.ts
@@ -0,0 +1,40 @@
+import Util = require("util");
+
+export interface LdapConfiguration {
+  url: string;
+  base_dn: string;
+
+  additional_users_dn?: string;
+  users_filter?: string;
+
+  additional_groups_dn?: string;
+  groups_filter?: string;
+
+  group_name_attribute?: string;
+  mail_attribute?: string;
+
+  user: string; // admin username
+  password: string; // admin password
+}
+
+export function complete(configuration: LdapConfiguration): LdapConfiguration {
+  const newConfiguration: LdapConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.users_filter) {
+    newConfiguration.users_filter = "cn={0}";
+  }
+
+  if (!newConfiguration.groups_filter) {
+    newConfiguration.groups_filter = "member={dn}";
+  }
+
+  if (!newConfiguration.group_name_attribute) {
+    newConfiguration.group_name_attribute = "cn";
+  }
+
+  if (!newConfiguration.mail_attribute) {
+    newConfiguration.mail_attribute = "mail";
+  }
+
+  return newConfiguration;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/NotifierConfiguration.spec.ts b/themes/triangles/server/src/lib/configuration/schema/NotifierConfiguration.spec.ts
new file mode 100644
index 00000000..6c576e8e
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/NotifierConfiguration.spec.ts
@@ -0,0 +1,40 @@
+import Assert = require("assert");
+import { NotifierConfiguration, complete } from "./NotifierConfiguration";
+
+describe("configuration/schema/NotifierConfiguration", function() {
+  it("should use a default notifier when none is provided", function() {
+    const configuration: NotifierConfiguration = {};
+    const [newConfiguration, error] = complete(configuration);
+
+    Assert.deepEqual(newConfiguration.filesystem, {filename: "/tmp/authelia/notification.txt"});
+  });
+
+  it("should ensure correct key is provided", function() {
+    const configuration = {
+      abc: "badvalue"
+    };
+    const [newConfiguration, error] = complete(configuration as any);
+
+    Assert.equal(error, "Notifier must have one of the following keys: 'filesystem', 'email' or 'smtp'");
+  });
+
+  it("should ensure there is no more than one key", function() {
+    const configuration: NotifierConfiguration = {
+      smtp: {
+        host: "smtp.example.com",
+        port: 25,
+        secure: false,
+        sender: "test@example.com"
+      },
+      email: {
+        username: "test",
+        password: "test",
+        sender: "test@example.com",
+        service: "gmail"
+      }
+    };
+    const [newConfiguration, error] = complete(configuration);
+
+    Assert.equal(error, "Notifier must have one of the following keys: 'filesystem', 'email' or 'smtp'");
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/NotifierConfiguration.ts b/themes/triangles/server/src/lib/configuration/schema/NotifierConfiguration.ts
new file mode 100644
index 00000000..7bcce15c
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/NotifierConfiguration.ts
@@ -0,0 +1,45 @@
+
+export interface EmailNotifierConfiguration {
+  username: string;
+  password: string;
+  sender: string;
+  service: string;
+}
+
+export interface SmtpNotifierConfiguration {
+  username?: string;
+  password?: string;
+  host: string;
+  port: number;
+  secure: boolean;
+  sender: string;
+}
+
+export interface FileSystemNotifierConfiguration {
+  filename: string;
+}
+
+export interface NotifierConfiguration {
+  email?: EmailNotifierConfiguration;
+  smtp?: SmtpNotifierConfiguration;
+  filesystem?: FileSystemNotifierConfiguration;
+}
+
+export function complete(configuration: NotifierConfiguration): [NotifierConfiguration, string] {
+  const newConfiguration: NotifierConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (Object.keys(newConfiguration).length == 0)
+    newConfiguration.filesystem = { filename: "/tmp/authelia/notification.txt" };
+
+  const ERROR = "Notifier must have one of the following keys: 'filesystem', 'email' or 'smtp'";
+
+  if (Object.keys(newConfiguration).length != 1)
+    return [newConfiguration, ERROR];
+
+  const key = Object.keys(newConfiguration)[0];
+
+  if (key != "filesystem" && key != "smtp" && key != "email")
+    return [newConfiguration, ERROR];
+
+  return [newConfiguration, undefined];
+}
diff --git a/themes/triangles/server/src/lib/configuration/schema/RegulationConfiguration.spec.ts b/themes/triangles/server/src/lib/configuration/schema/RegulationConfiguration.spec.ts
new file mode 100644
index 00000000..dce2caf4
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/RegulationConfiguration.spec.ts
@@ -0,0 +1,13 @@
+import Assert = require("assert");
+import { RegulationConfiguration, complete } from "./RegulationConfiguration";
+
+describe("configuration/schema/RegulationConfiguration", function() {
+  it("should return default regulation configuration", function() {
+    const configuration: RegulationConfiguration = {};
+    const newConfiguration = complete(configuration);
+
+    Assert.equal(newConfiguration.ban_time, 300);
+    Assert.equal(newConfiguration.find_time, 120);
+    Assert.equal(newConfiguration.max_retries, 3);
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/RegulationConfiguration.ts b/themes/triangles/server/src/lib/configuration/schema/RegulationConfiguration.ts
new file mode 100644
index 00000000..117463f4
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/RegulationConfiguration.ts
@@ -0,0 +1,23 @@
+export interface RegulationConfiguration {
+  max_retries?: number;
+  find_time?: number;
+  ban_time?: number;
+}
+
+export function complete(configuration: RegulationConfiguration): RegulationConfiguration {
+  const newConfiguration: RegulationConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.max_retries) {
+    newConfiguration.max_retries = 3;
+  }
+
+  if (!newConfiguration.find_time) {
+    newConfiguration.find_time = 120; // seconds
+  }
+
+  if (!newConfiguration.ban_time) {
+    newConfiguration.ban_time = 300; // seconds
+  }
+
+  return newConfiguration;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/SessionConfiguration.spec.ts b/themes/triangles/server/src/lib/configuration/schema/SessionConfiguration.spec.ts
new file mode 100644
index 00000000..e5401083
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/SessionConfiguration.spec.ts
@@ -0,0 +1,16 @@
+import Assert = require("assert");
+import { SessionConfiguration, complete } from "./SessionConfiguration";
+
+describe("configuration/schema/SessionConfiguration", function() {
+  it("should return default regulation configuration", function() {
+    const configuration: SessionConfiguration = {
+      domain: "example.com",
+      secret: "unsecure_secret"
+    };
+    const newConfiguration = complete(configuration);
+
+    Assert.equal(newConfiguration.name, 'authelia_session');
+    Assert.equal(newConfiguration.expiration, 3600000);
+    Assert.equal(newConfiguration.inactivity, undefined);
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/SessionConfiguration.ts b/themes/triangles/server/src/lib/configuration/schema/SessionConfiguration.ts
new file mode 100644
index 00000000..2c88bb21
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/SessionConfiguration.ts
@@ -0,0 +1,32 @@
+export interface SessionRedisOptions {
+  host: string;
+  port: number;
+  password?: string;
+}
+
+export interface SessionConfiguration {
+  name?: string;
+  domain: string;
+  secret: string;
+  expiration?: number;
+  inactivity?: number;
+  redis?: SessionRedisOptions;
+}
+
+export function complete(configuration: SessionConfiguration): SessionConfiguration {
+  const newConfiguration: SessionConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.name) {
+    newConfiguration.name = "authelia_session";
+  }
+
+  if (!newConfiguration.expiration) {
+    newConfiguration.expiration = 3600000; // 1 hour
+  }
+
+  if (!newConfiguration.inactivity) {
+    newConfiguration.inactivity = undefined; // disabled
+  }
+
+  return newConfiguration;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/StorageConfiguration.spec.ts b/themes/triangles/server/src/lib/configuration/schema/StorageConfiguration.spec.ts
new file mode 100644
index 00000000..9d02a11b
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/StorageConfiguration.spec.ts
@@ -0,0 +1,15 @@
+import Assert = require("assert");
+import { StorageConfiguration, complete } from "./StorageConfiguration";
+
+describe("configuration/schema/StorageConfiguration", function() {
+  it("should return default regulation configuration", function() {
+    const configuration: StorageConfiguration = {};
+    const newConfiguration = complete(configuration);
+
+    Assert.deepEqual(newConfiguration, {
+      local: {
+        in_memory: true
+      }
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/StorageConfiguration.ts b/themes/triangles/server/src/lib/configuration/schema/StorageConfiguration.ts
new file mode 100644
index 00000000..47e356ef
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/StorageConfiguration.ts
@@ -0,0 +1,30 @@
+export interface MongoStorageConfiguration {
+  url: string;
+  database: string;
+  auth?: {
+    username: string;
+    password: string;
+  };
+}
+
+export interface LocalStorageConfiguration {
+  path?: string;
+  in_memory?: boolean;
+}
+
+export interface StorageConfiguration {
+  local?: LocalStorageConfiguration;
+  mongo?: MongoStorageConfiguration;
+}
+
+export function complete(configuration: StorageConfiguration): StorageConfiguration {
+  const newConfiguration: StorageConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.local && !newConfiguration.mongo) {
+    newConfiguration.local = {
+      in_memory: true
+    };
+  }
+
+  return newConfiguration;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/TotpConfiguration.ts b/themes/triangles/server/src/lib/configuration/schema/TotpConfiguration.ts
new file mode 100644
index 00000000..68313563
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/TotpConfiguration.ts
@@ -0,0 +1,13 @@
+export interface TotpConfiguration {
+  issuer: string;
+}
+
+export function complete(configuration: TotpConfiguration): TotpConfiguration {
+  const newConfiguration: TotpConfiguration = (configuration) ? JSON.parse(JSON.stringify(configuration)) : {};
+
+  if (!newConfiguration.issuer) {
+    newConfiguration.issuer = "authelia.com";
+  }
+
+  return newConfiguration;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/configuration/schema/UserDatabaseConfiguration.ts b/themes/triangles/server/src/lib/configuration/schema/UserDatabaseConfiguration.ts
new file mode 100644
index 00000000..8008b483
--- /dev/null
+++ b/themes/triangles/server/src/lib/configuration/schema/UserDatabaseConfiguration.ts
@@ -0,0 +1,9 @@
+
+export interface UserInfo {
+  username: string;
+  password_hash: string;
+  email: string;
+  groups?: string[];
+}
+
+export type UserDatabaseConfiguration = UserInfo[];
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/connectors/mongo/IMongoClient.d.ts b/themes/triangles/server/src/lib/connectors/mongo/IMongoClient.d.ts
new file mode 100644
index 00000000..36cb4b8b
--- /dev/null
+++ b/themes/triangles/server/src/lib/connectors/mongo/IMongoClient.d.ts
@@ -0,0 +1,6 @@
+import MongoDB = require("mongodb");
+import Bluebird = require("bluebird");
+
+export interface IMongoClient {
+    collection(name: string): Bluebird<MongoDB.Collection>
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/connectors/mongo/MongoClient.spec.ts b/themes/triangles/server/src/lib/connectors/mongo/MongoClient.spec.ts
new file mode 100644
index 00000000..ca0c6859
--- /dev/null
+++ b/themes/triangles/server/src/lib/connectors/mongo/MongoClient.spec.ts
@@ -0,0 +1,119 @@
+import Assert = require("assert");
+import Bluebird = require("bluebird");
+import MongoDB = require("mongodb");
+import Sinon = require("sinon");
+
+import { MongoClient } from "./MongoClient";
+import { GlobalLoggerStub } from "../../logging/GlobalLoggerStub.spec";
+import { MongoStorageConfiguration } from "../../configuration/schema/StorageConfiguration";
+
+describe("connectors/mongo/MongoClient", function () {
+  let MongoClientStub: any;
+  let mongoClientStub: any;
+  let mongoDatabaseStub: any;
+  let logger: GlobalLoggerStub = new GlobalLoggerStub();
+
+  const configuration: MongoStorageConfiguration = {
+    url: "mongo://url",
+    database: "databasename"
+  };
+
+  describe("connection", () => {
+    before(() => {
+      mongoClientStub = {
+        db: Sinon.stub()
+      };
+      mongoDatabaseStub = {
+        on: Sinon.stub(),
+        collection: Sinon.stub()
+      }
+      MongoClientStub = Sinon.stub(
+        MongoDB.MongoClient, "connect");
+      MongoClientStub.yields(
+        undefined, mongoClientStub);
+      mongoClientStub.db.returns(
+        mongoDatabaseStub);
+    });
+
+    after(() => {
+      MongoClientStub.restore();
+    });
+
+    it("should use credentials from configuration", () => {
+      configuration.auth = {
+        username: "authelia",
+        password: "authelia_pass"
+      };
+
+      const client = new MongoClient(configuration, logger);
+      return client.collection("test")
+        .then(() => {
+          Assert(MongoClientStub.calledWith("mongo://url", {
+            auth: {
+              user: "authelia",
+              password: "authelia_pass"
+            }
+          }))
+        });
+    });
+  });
+
+  describe("collection", () => {
+    before(function() {
+      mongoClientStub = {
+        db: Sinon.stub()
+      };
+      mongoDatabaseStub = {
+        on: Sinon.stub(),
+        collection: Sinon.stub()
+      }
+    });
+
+    describe("Connection to mongo is ok", function() {
+      before(function () {
+        MongoClientStub = Sinon.stub(
+          MongoDB.MongoClient, "connect");
+        MongoClientStub.yields(
+          undefined, mongoClientStub);
+        mongoClientStub.db.returns(
+          mongoDatabaseStub);
+      });
+  
+      after(function () {
+        MongoClientStub.restore();
+      });
+  
+      it("should create a collection", function () {
+        const COLLECTION_NAME = "mycollection";
+        const client = new MongoClient(configuration, logger);
+  
+        mongoDatabaseStub.collection.returns("COL");
+        return client.collection(COLLECTION_NAME)
+          .then((collection) => mongoDatabaseStub.collection.calledWith(COLLECTION_NAME));
+      });
+    });
+
+    describe("Connection to mongo is broken", function() {
+      before(function () {
+        MongoClientStub = Sinon.stub(
+          MongoDB.MongoClient, "connect");
+        MongoClientStub.yields(
+          new Error("Failed connection"), undefined);
+      });
+  
+      after(function () {
+        MongoClientStub.restore();
+      });
+
+      it("should fail creating the collection", function() {
+        const COLLECTION_NAME = "mycollection";
+        const client = new MongoClient(configuration, logger);
+  
+        mongoDatabaseStub.collection.returns("COL");
+        return client.collection(COLLECTION_NAME)
+          .then((collection) => Bluebird.reject(new Error("should not be here.")))
+          .catch((err) => Bluebird.resolve());
+      });
+    })
+  });
+});
diff --git a/themes/triangles/server/src/lib/connectors/mongo/MongoClient.ts b/themes/triangles/server/src/lib/connectors/mongo/MongoClient.ts
new file mode 100644
index 00000000..d15731e9
--- /dev/null
+++ b/themes/triangles/server/src/lib/connectors/mongo/MongoClient.ts
@@ -0,0 +1,76 @@
+
+import MongoDB = require("mongodb");
+import { IMongoClient } from "./IMongoClient";
+import Bluebird = require("bluebird");
+import { AUTHENTICATION_FAILED } from "../../../../../shared/UserMessages";
+import { IGlobalLogger } from "../../logging/IGlobalLogger";
+import { MongoStorageConfiguration } from "../../configuration/schema/StorageConfiguration";
+
+export class MongoClient implements IMongoClient {
+  private configuration: MongoStorageConfiguration;
+
+  private database: MongoDB.Db;
+  private client: MongoDB.MongoClient;
+  private logger: IGlobalLogger;
+
+  constructor(
+    configuration: MongoStorageConfiguration,
+    logger: IGlobalLogger) {
+
+    this.configuration = configuration;
+    this.logger = logger;
+  }
+
+  connect(): Bluebird<void> {
+    const that = this;
+    const options: MongoDB.MongoClientOptions = {};
+    if (that.configuration.auth) {
+      options["auth"] = {
+        user: that.configuration.auth.username,
+        password: that.configuration.auth.password
+      };
+    }
+
+    return new Bluebird((resolve, reject) => {
+        MongoDB.MongoClient.connect(
+          this.configuration.url,
+          options,
+          function(err, client) {
+          if (err) {
+            reject(err);
+            return;
+          }
+          resolve(client);
+        });
+      })
+      .then(function (client: MongoDB.MongoClient) {
+        that.database = client.db(that.configuration.database);
+        that.database.on("close", () => {
+          that.logger.info("[MongoClient] Lost connection.");
+        });
+        that.database.on("reconnect", () => {
+          that.logger.info("[MongoClient] Reconnected.");
+        });
+        that.client = client;
+      });
+  }
+
+  close(): Bluebird<void> {
+    if (this.client) {
+      this.client.close();
+      this.database = undefined;
+      this.client = undefined;
+    }
+    return Bluebird.resolve();
+  }
+
+  collection(name: string): Bluebird<MongoDB.Collection> {
+    if (!this.client) {
+      const that = this;
+      return this.connect()
+        .then(() => Bluebird.resolve(that.database.collection(name)));
+    }
+
+    return Bluebird.resolve(this.database.collection(name));
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/connectors/mongo/MongoClientStub.spec.ts b/themes/triangles/server/src/lib/connectors/mongo/MongoClientStub.spec.ts
new file mode 100644
index 00000000..1cfd48e3
--- /dev/null
+++ b/themes/triangles/server/src/lib/connectors/mongo/MongoClientStub.spec.ts
@@ -0,0 +1,16 @@
+import Sinon = require("sinon");
+import MongoDB = require("mongodb");
+import Bluebird = require("bluebird");
+import { IMongoClient } from "../../../../src/lib/connectors/mongo/IMongoClient";
+
+export class MongoClientStub implements IMongoClient {
+  public collectionStub: Sinon.SinonStub;
+
+  constructor() {
+    this.collectionStub = Sinon.stub();
+  }
+
+  collection(name: string): Bluebird<MongoDB.Collection> {
+    return this.collectionStub(name);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/logging/GlobalLogger.ts b/themes/triangles/server/src/lib/logging/GlobalLogger.ts
new file mode 100644
index 00000000..4da7acf4
--- /dev/null
+++ b/themes/triangles/server/src/lib/logging/GlobalLogger.ts
@@ -0,0 +1,34 @@
+import { IGlobalLogger } from "./IGlobalLogger";
+import Util = require("util");
+import Express = require("express");
+import Winston = require("winston");
+
+declare module "express" {
+  interface Request {
+    id: string;
+  }
+}
+
+export class GlobalLogger implements IGlobalLogger {
+  private winston: typeof Winston;
+  constructor(winston: typeof Winston) {
+    this.winston = winston;
+  }
+
+  private buildMessage(message: string, ...args: any[]): string {
+    return Util.format("date='%s' message='%s'", new Date(),
+      Util.format(message, ...args));
+  }
+
+  info(message: string, ...args: any[]): void {
+    this.winston.info(this.buildMessage(message, ...args));
+  }
+
+  debug(message: string, ...args: any[]): void {
+    this.winston.debug(this.buildMessage(message, ...args));
+  }
+
+  error(message: string, ...args: any[]): void {
+    this.winston.debug(this.buildMessage(message, ...args));
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/logging/GlobalLoggerStub.spec.ts b/themes/triangles/server/src/lib/logging/GlobalLoggerStub.spec.ts
new file mode 100644
index 00000000..d4bb1371
--- /dev/null
+++ b/themes/triangles/server/src/lib/logging/GlobalLoggerStub.spec.ts
@@ -0,0 +1,38 @@
+import Sinon = require("sinon");
+import { GlobalLogger } from "./GlobalLogger";
+import Winston = require("winston");
+import Express = require("express");
+import { IGlobalLogger } from "./IGlobalLogger";
+
+export class GlobalLoggerStub implements IGlobalLogger {
+  infoStub: Sinon.SinonStub;
+  debugStub: Sinon.SinonStub;
+  errorStub: Sinon.SinonStub;
+  private globalLogger: IGlobalLogger;
+
+  constructor(enableLogging?: boolean) {
+    this.infoStub = Sinon.stub();
+    this.debugStub = Sinon.stub();
+    this.errorStub = Sinon.stub();
+    if (enableLogging)
+      this.globalLogger = new GlobalLogger(Winston);
+  }
+
+  info(message: string, ...args: any[]): void {
+    if (this.globalLogger)
+      this.globalLogger.info(message, ...args);
+    this.infoStub(message, ...args);
+  }
+
+  debug(message: string, ...args: any[]): void {
+    if (this.globalLogger)
+      this.globalLogger.info(message, ...args);
+    this.debugStub(message, ...args);
+  }
+
+  error(message: string, ...args: any[]): void {
+    if (this.globalLogger)
+      this.globalLogger.info(message, ...args);
+    this.errorStub(message, ...args);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/logging/IGlobalLogger.ts b/themes/triangles/server/src/lib/logging/IGlobalLogger.ts
new file mode 100644
index 00000000..548515ec
--- /dev/null
+++ b/themes/triangles/server/src/lib/logging/IGlobalLogger.ts
@@ -0,0 +1,5 @@
+export interface IGlobalLogger {
+  info(message: string, ...args: any[]): void;
+  debug(message: string, ...args: any[]): void;
+  error(message: string, ...args: any[]): void;
+}
diff --git a/themes/triangles/server/src/lib/logging/IRequestLogger.ts b/themes/triangles/server/src/lib/logging/IRequestLogger.ts
new file mode 100644
index 00000000..126a601f
--- /dev/null
+++ b/themes/triangles/server/src/lib/logging/IRequestLogger.ts
@@ -0,0 +1,7 @@
+import Express = require("express");
+
+export interface IRequestLogger {
+  info(req: Express.Request, message: string, ...args: any[]): void;
+  debug(req: Express.Request, message: string, ...args: any[]): void;
+  error(req: Express.Request, message: string, ...args: any[]): void;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/logging/RequestLogger.ts b/themes/triangles/server/src/lib/logging/RequestLogger.ts
new file mode 100644
index 00000000..c45c6601
--- /dev/null
+++ b/themes/triangles/server/src/lib/logging/RequestLogger.ts
@@ -0,0 +1,45 @@
+import { IRequestLogger } from "./IRequestLogger";
+import Util = require("util");
+import Express = require("express");
+import Winston = require("winston");
+
+declare module "express" {
+  interface Request {
+    id: string;
+  }
+}
+
+export class RequestLogger implements IRequestLogger {
+  private winston: typeof Winston;
+
+  constructor(winston: typeof Winston) {
+    this.winston = winston;
+  }
+
+  private formatHeader(req: Express.Request) {
+    const clientIP = req.ip; // The IP of the original client going through the proxy chain.
+    return Util.format("date='%s' method='%s', path='%s' requestId='%s' sessionId='%s' ip='%s'",
+      new Date(), req.method, req.path, req.id, req.sessionID, clientIP);
+  }
+
+  private formatBody(message: string) {
+    return Util.format("message='%s'", message);
+  }
+
+  private formatMessage(req: Express.Request, message: string) {
+    return Util.format("%s %s", this.formatHeader(req),
+      this.formatBody(message));
+  }
+
+  info(req: Express.Request, message: string, ...args: any[]): void {
+    this.winston.info(this.formatMessage(req, message), ...args);
+  }
+
+  debug(req: Express.Request, message: string, ...args: any[]): void {
+    this.winston.debug(this.formatMessage(req, message), ...args);
+  }
+
+  error(req: Express.Request, message: string, ...args: any[]): void {
+    this.winston.error(this.formatMessage(req, message), ...args);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/logging/RequestLoggerStub.spec.ts b/themes/triangles/server/src/lib/logging/RequestLoggerStub.spec.ts
new file mode 100644
index 00000000..b0e37521
--- /dev/null
+++ b/themes/triangles/server/src/lib/logging/RequestLoggerStub.spec.ts
@@ -0,0 +1,38 @@
+import { IRequestLogger } from "./IRequestLogger";
+import Sinon = require("sinon");
+import { RequestLogger } from "./RequestLogger";
+import Winston = require("winston");
+import Express = require("express");
+
+export class RequestLoggerStub implements IRequestLogger {
+  infoStub: Sinon.SinonStub;
+  debugStub: Sinon.SinonStub;
+  errorStub: Sinon.SinonStub;
+  private requestLogger: RequestLogger;
+
+  constructor(enableLogging?: boolean) {
+    this.infoStub = Sinon.stub();
+    this.debugStub = Sinon.stub();
+    this.errorStub = Sinon.stub();
+    if (enableLogging)
+      this.requestLogger = new RequestLogger(Winston);
+  }
+
+  info(req: Express.Request, message: string, ...args: any[]): void {
+    if (this.requestLogger)
+      this.requestLogger.info(req, message, ...args);
+    this.infoStub(req, message, ...args);
+  }
+
+  debug(req: Express.Request, message: string, ...args: any[]): void {
+    if (this.requestLogger)
+      this.requestLogger.info(req, message, ...args);
+    this.debugStub(req, message, ...args);
+  }
+
+  error(req: Express.Request, message: string, ...args: any[]): void {
+    if (this.requestLogger)
+      this.requestLogger.info(req, message, ...args);
+    this.errorStub(req, message, ...args);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/AbstractEmailNotifier.ts b/themes/triangles/server/src/lib/notifiers/AbstractEmailNotifier.ts
new file mode 100644
index 00000000..198e4e5d
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/AbstractEmailNotifier.ts
@@ -0,0 +1,23 @@
+
+import { INotifier } from "../notifiers/INotifier";
+import { Identity } from "../../../types/Identity";
+
+import Fs = require("fs");
+import Path = require("path");
+import Ejs = require("ejs");
+import BluebirdPromise = require("bluebird");
+
+const email_template = Fs.readFileSync(Path.join(__dirname, "../../resources/email-template.ejs"), "UTF-8");
+
+export abstract class AbstractEmailNotifier implements INotifier {
+  notify(to: string, subject: string, link: string): BluebirdPromise<void> {
+    const d = {
+      url: link,
+      button_title: "Continue",
+      title: subject
+    };
+    return this.sendEmail(to, subject, Ejs.render(email_template, d));
+  }
+
+  abstract sendEmail(to: string, subject: string, content: string): BluebirdPromise<void>;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/EmailNotifier.spec.ts b/themes/triangles/server/src/lib/notifiers/EmailNotifier.spec.ts
new file mode 100644
index 00000000..8211bbc0
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/EmailNotifier.spec.ts
@@ -0,0 +1,54 @@
+import * as sinon from "sinon";
+import * as Assert from "assert";
+import BluebirdPromise = require("bluebird");
+
+import { MailSenderStub } from "./MailSenderStub.spec";
+import EmailNotifier = require("./EmailNotifier");
+
+
+describe("notifiers/EmailNotifier", function () {
+  it("should send an email to given user", function () {
+    const mailSender = new MailSenderStub();
+    const options = {
+      username: "user_gmail",
+      password: "pass_gmail",
+      sender: "admin@example.com",
+      service: "gmail"
+    };
+
+    mailSender.sendStub.returns(BluebirdPromise.resolve());
+    const sender = new EmailNotifier.EmailNotifier(options, mailSender);
+    const subject = "subject";
+    const url = "http://test.com";
+
+    return sender.notify("user@example.com", subject, url)
+      .then(function () {
+        Assert.equal(mailSender.sendStub.getCall(0).args[0].to, "user@example.com");
+        Assert.equal(mailSender.sendStub.getCall(0).args[0].subject, "subject");
+        return BluebirdPromise.resolve();
+      });
+  });
+
+  it("should fail while sending an email", function () {
+    const mailSender = new MailSenderStub();
+    const options = {
+      username: "user_gmail",
+      password: "pass_gmail",
+      sender: "admin@example.com",
+      service: "gmail"
+    };
+
+    mailSender.sendStub.returns(BluebirdPromise.reject(new Error("Failed to send mail")));
+    const sender = new EmailNotifier.EmailNotifier(options, mailSender);
+    const subject = "subject";
+    const url = "http://test.com";
+
+    return sender.notify("user@example.com", subject, url)
+      .then(function () {
+        return BluebirdPromise.reject(new Error());
+      }, function() {
+        Assert.equal(mailSender.sendStub.getCall(0).args[0].from, "admin@example.com");
+        return BluebirdPromise.resolve();
+      });
+  });
+});
diff --git a/themes/triangles/server/src/lib/notifiers/EmailNotifier.ts b/themes/triangles/server/src/lib/notifiers/EmailNotifier.ts
new file mode 100644
index 00000000..4df7c861
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/EmailNotifier.ts
@@ -0,0 +1,27 @@
+
+import * as BluebirdPromise from "bluebird";
+
+import { AbstractEmailNotifier } from "../notifiers/AbstractEmailNotifier";
+import { EmailNotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+import { IMailSender } from "./IMailSender";
+
+export class EmailNotifier extends AbstractEmailNotifier {
+  private mailSender: IMailSender;
+  private sender: string;
+
+  constructor(options: EmailNotifierConfiguration, mailSender: IMailSender) {
+    super();
+    this.mailSender = mailSender;
+    this.sender = options.sender;
+  }
+
+  sendEmail(to: string, subject: string, content: string) {
+    const mailOptions = {
+      from: this.sender,
+      to: to,
+      subject: subject,
+      html: content
+    };
+    return this.mailSender.send(mailOptions);
+  }
+}
diff --git a/themes/triangles/server/src/lib/notifiers/FileSystemNotifier.ts b/themes/triangles/server/src/lib/notifiers/FileSystemNotifier.ts
new file mode 100644
index 00000000..23f6242c
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/FileSystemNotifier.ts
@@ -0,0 +1,22 @@
+import * as BluebirdPromise from "bluebird";
+import * as util from "util";
+import * as Fs from "fs";
+import { INotifier } from "./INotifier";
+import { Identity } from "../../../types/Identity";
+
+import { FileSystemNotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+
+export class FileSystemNotifier implements INotifier {
+  private filename: string;
+
+  constructor(options: FileSystemNotifierConfiguration) {
+    this.filename = options.filename;
+  }
+
+  notify(to: string, subject: string, link: string): BluebirdPromise<void> {
+    const content = util.format("Date: %s\nEmail: %s\nSubject: %s\nLink: %s",
+      new Date().toString(), to, subject, link);
+    const writeFilePromised: any = BluebirdPromise.promisify(Fs.writeFile);
+    return writeFilePromised(this.filename, content);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/IMailSender.ts b/themes/triangles/server/src/lib/notifiers/IMailSender.ts
new file mode 100644
index 00000000..34ac464a
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/IMailSender.ts
@@ -0,0 +1,6 @@
+import BluebirdPromise = require("bluebird");
+import Nodemailer = require("nodemailer");
+
+export interface IMailSender {
+  send(mailOptions: Nodemailer.SendMailOptions): BluebirdPromise<void>;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/IMailSenderBuilder.ts b/themes/triangles/server/src/lib/notifiers/IMailSenderBuilder.ts
new file mode 100644
index 00000000..36d4dcdf
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/IMailSenderBuilder.ts
@@ -0,0 +1,7 @@
+import { IMailSender } from "./IMailSender";
+import { SmtpNotifierConfiguration, EmailNotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+
+export interface IMailSenderBuilder {
+  buildEmail(options: EmailNotifierConfiguration): IMailSender;
+  buildSmtp(options: SmtpNotifierConfiguration): IMailSender;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/INotifier.ts b/themes/triangles/server/src/lib/notifiers/INotifier.ts
new file mode 100644
index 00000000..b9a6b138
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/INotifier.ts
@@ -0,0 +1,5 @@
+import * as BluebirdPromise from "bluebird";
+
+export interface INotifier {
+  notify(to: string, subject: string, link: string): BluebirdPromise<void>;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/MailSender.ts b/themes/triangles/server/src/lib/notifiers/MailSender.ts
new file mode 100644
index 00000000..536a88e6
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/MailSender.ts
@@ -0,0 +1,42 @@
+import { IMailSender } from "./IMailSender";
+import Nodemailer = require("nodemailer");
+import NodemailerDirectTransport = require("nodemailer-direct-transport");
+import NodemailerSmtpTransport = require("nodemailer-smtp-transport");
+import BluebirdPromise = require("bluebird");
+
+export class MailSender implements IMailSender {
+  private transporter: Nodemailer.Transporter;
+
+  constructor(options: NodemailerDirectTransport.DirectOptions |
+    NodemailerSmtpTransport.SmtpOptions, nodemailer: typeof Nodemailer) {
+    this.transporter = nodemailer.createTransport(options);
+  }
+
+  verify(): BluebirdPromise<void> {
+    const that = this;
+    return new BluebirdPromise(function (resolve, reject) {
+      that.transporter.verify(function (error: Error, success: any) {
+        if (error) {
+          reject(new Error("Unable to connect to SMTP server. \
+  Please check the service is running and your credentials are correct."));
+          return;
+        }
+        resolve();
+      });
+    });
+  }
+
+  send(mailOptions: Nodemailer.SendMailOptions): BluebirdPromise<void> {
+    const that = this;
+    return new BluebirdPromise(function (resolve, reject) {
+      that.transporter.sendMail(mailOptions, (error: Error,
+        data: Nodemailer.SentMessageInfo) => {
+        if (error) {
+          reject(new Error("Error while sending email: " + error.message));
+          return;
+        }
+        resolve();
+      });
+    });
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/MailSenderBuilder.spec.ts b/themes/triangles/server/src/lib/notifiers/MailSenderBuilder.spec.ts
new file mode 100644
index 00000000..41e0db42
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/MailSenderBuilder.spec.ts
@@ -0,0 +1,67 @@
+
+import { MailSenderBuilder } from ".//MailSenderBuilder";
+import Nodemailer = require("nodemailer");
+import Sinon = require("sinon");
+import Assert = require("assert");
+
+describe("notifiers/MailSenderBuilder", function() {
+  let createTransportStub: Sinon.SinonStub;
+  beforeEach(function() {
+    createTransportStub = Sinon.stub(Nodemailer, "createTransport");
+  });
+
+  afterEach(function() {
+    createTransportStub.restore();
+  });
+
+  it("should create a email mail sender", function() {
+    const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
+    mailSenderBuilder.buildEmail({
+      username: "user_gmail",
+      password: "pass_gmail",
+      sender: "admin@example.com",
+      service: "gmail"
+    });
+    Assert.equal(createTransportStub.getCall(0).args[0].auth.user, "user_gmail");
+    Assert.equal(createTransportStub.getCall(0).args[0].auth.pass, "pass_gmail");
+    Assert.equal(createTransportStub.getCall(0).args[0].service, "gmail");
+  });
+
+  describe("build smtp mail sender", function() {
+    it("should create a smtp mail sender with authenticated user", function() {
+      const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
+      mailSenderBuilder.buildSmtp({
+        host: "mail.example.com",
+        password: "password",
+        port: 25,
+        secure: true,
+        username: "user",
+        sender: "admin@example.com"
+      });
+      Assert.deepStrictEqual(createTransportStub.getCall(0).args[0], {
+        host: "mail.example.com",
+        auth: {
+          pass: "password",
+          user: "user"
+        },
+        port: 25,
+        secure: true,
+      });
+    });
+
+    it("should create a smtp mail sender with anonymous user", function() {
+      const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
+      mailSenderBuilder.buildSmtp({
+        host: "mail.example.com",
+        port: 25,
+        secure: true,
+        sender: "admin@example.com"
+      });
+      Assert.deepStrictEqual(createTransportStub.getCall(0).args[0], {
+        host: "mail.example.com",
+        port: 25,
+        secure: true,
+      });
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/MailSenderBuilder.ts b/themes/triangles/server/src/lib/notifiers/MailSenderBuilder.ts
new file mode 100644
index 00000000..1d06be52
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/MailSenderBuilder.ts
@@ -0,0 +1,42 @@
+import { IMailSender } from "./IMailSender";
+import { IMailSenderBuilder } from "./IMailSenderBuilder";
+import { MailSender } from "./MailSender";
+import Nodemailer = require("nodemailer");
+import NodemailerSmtpTransport = require("nodemailer-smtp-transport");
+import { SmtpNotifierConfiguration, EmailNotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+
+export class MailSenderBuilder implements IMailSenderBuilder {
+  private nodemailer: typeof Nodemailer;
+
+  constructor(nodemailer: typeof Nodemailer) {
+    this.nodemailer = nodemailer;
+  }
+
+  buildEmail(options: EmailNotifierConfiguration): IMailSender {
+    const emailOptions = {
+      service: options.service,
+      auth: {
+        user: options.username,
+        pass: options.password
+      }
+    };
+    return new MailSender(emailOptions, this.nodemailer);
+  }
+
+  buildSmtp(options: SmtpNotifierConfiguration): IMailSender {
+    const smtpOptions: NodemailerSmtpTransport.SmtpOptions = {
+      host: options.host,
+      port: options.port,
+      secure: options.secure, // upgrade later with STARTTLS
+    };
+
+    if (options.username && options.password) {
+      smtpOptions.auth = {
+        user: options.username,
+        pass: options.password
+      };
+    }
+
+    return new MailSender(smtpOptions, this.nodemailer);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/MailSenderBuilderStub.spec.ts b/themes/triangles/server/src/lib/notifiers/MailSenderBuilderStub.spec.ts
new file mode 100644
index 00000000..5b76f6e5
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/MailSenderBuilderStub.spec.ts
@@ -0,0 +1,25 @@
+import { IMailSenderBuilder } from "../../../src/lib/notifiers/IMailSenderBuilder";
+import BluebirdPromise = require("bluebird");
+import Nodemailer = require("nodemailer");
+import Sinon = require("sinon");
+import { IMailSender } from "../../../src/lib/notifiers/IMailSender";
+import { SmtpNotifierConfiguration, EmailNotifierConfiguration } from "../../../src/lib/configuration/schema/NotifierConfiguration";
+
+export class MailSenderBuilderStub implements IMailSenderBuilder {
+  buildEmailStub: Sinon.SinonStub;
+  buildSmtpStub: Sinon.SinonStub;
+
+  constructor() {
+    this.buildEmailStub = Sinon.stub();
+    this.buildSmtpStub = Sinon.stub();
+  }
+
+  buildEmail(options: EmailNotifierConfiguration): IMailSender {
+    return this.buildEmailStub(options);
+  }
+
+  buildSmtp(options: SmtpNotifierConfiguration): IMailSender {
+    return this.buildSmtpStub(options);
+  }
+
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/MailSenderStub.spec.ts b/themes/triangles/server/src/lib/notifiers/MailSenderStub.spec.ts
new file mode 100644
index 00000000..d57c458f
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/MailSenderStub.spec.ts
@@ -0,0 +1,16 @@
+import { IMailSender } from "../../../src/lib/notifiers/IMailSender";
+import BluebirdPromise = require("bluebird");
+import Nodemailer = require("nodemailer");
+import Sinon = require("sinon");
+
+export class MailSenderStub implements IMailSender {
+  sendStub: Sinon.SinonStub;
+
+  constructor() {
+    this.sendStub = Sinon.stub();
+  }
+
+  send(mailOptions: Nodemailer.SendMailOptions): BluebirdPromise<void> {
+    return this.sendStub(mailOptions);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/NotifierFactory.spec.ts b/themes/triangles/server/src/lib/notifiers/NotifierFactory.spec.ts
new file mode 100644
index 00000000..f15e7667
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/NotifierFactory.spec.ts
@@ -0,0 +1,42 @@
+
+import * as sinon from "sinon";
+import * as BluebirdPromise from "bluebird";
+import * as assert from "assert";
+
+import { NotifierFactory } from "./NotifierFactory";
+import { EmailNotifier } from "./EmailNotifier";
+import { SmtpNotifier } from "./SmtpNotifier";
+import { MailSenderBuilderStub } from "./MailSenderBuilderStub.spec";
+
+
+describe("notifiers/NotifierFactory", function () {
+  let mailSenderBuilderStub: MailSenderBuilderStub;
+  it("should build a Email Notifier", function () {
+    const options = {
+      email: {
+        username: "abc",
+        password: "password",
+        sender: "admin@example.com",
+        service: "gmail"
+      }
+    };
+    mailSenderBuilderStub = new MailSenderBuilderStub();
+    assert(NotifierFactory.build(options, mailSenderBuilderStub) instanceof EmailNotifier);
+  });
+
+  it("should build a SMTP Notifier", function () {
+    const options = {
+      smtp: {
+        username: "user",
+        password: "pass",
+        secure: true,
+        host: "localhost",
+        port: 25,
+        sender: "admin@example.com"
+      }
+    };
+
+    mailSenderBuilderStub = new MailSenderBuilderStub();
+    assert(NotifierFactory.build(options, mailSenderBuilderStub) instanceof SmtpNotifier);
+  });
+});
diff --git a/themes/triangles/server/src/lib/notifiers/NotifierFactory.ts b/themes/triangles/server/src/lib/notifiers/NotifierFactory.ts
new file mode 100644
index 00000000..a89155fe
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/NotifierFactory.ts
@@ -0,0 +1,33 @@
+
+import { NotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+import Nodemailer = require("nodemailer");
+import { INotifier } from "./INotifier";
+
+import { FileSystemNotifier } from "./FileSystemNotifier";
+import { EmailNotifier } from "./EmailNotifier";
+import { SmtpNotifier } from "./SmtpNotifier";
+import { IMailSender } from "./IMailSender";
+import { IMailSenderBuilder } from "./IMailSenderBuilder";
+
+export class NotifierFactory {
+  static build(options: NotifierConfiguration, mailSenderBuilder: IMailSenderBuilder): INotifier {
+    if ("email" in options) {
+      const mailSender = mailSenderBuilder.buildEmail(options.email);
+      return new EmailNotifier(options.email, mailSender);
+    }
+    else if ("smtp" in options) {
+      const mailSender = mailSenderBuilder.buildSmtp(options.smtp);
+      return new SmtpNotifier(options.smtp, mailSender);
+    }
+    else if ("filesystem" in options) {
+      return new FileSystemNotifier(options.filesystem);
+    }
+    else {
+      throw new Error("No available notifier option detected.");
+    }
+  }
+}
+
+
+
+
diff --git a/themes/triangles/server/src/lib/notifiers/NotifierStub.spec.ts b/themes/triangles/server/src/lib/notifiers/NotifierStub.spec.ts
new file mode 100644
index 00000000..f99231b5
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/NotifierStub.spec.ts
@@ -0,0 +1,16 @@
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+
+import { INotifier } from "./INotifier";
+
+export class NotifierStub implements INotifier {
+    notifyStub: Sinon.SinonStub;
+
+    constructor() {
+        this.notifyStub = Sinon.stub();
+    }
+
+    notify(to: string, subject: string, link: string): BluebirdPromise<void> {
+        return this.notifyStub(to, subject, link);
+    }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/notifiers/SmtpNotifier.ts b/themes/triangles/server/src/lib/notifiers/SmtpNotifier.ts
new file mode 100644
index 00000000..f93a6d4a
--- /dev/null
+++ b/themes/triangles/server/src/lib/notifiers/SmtpNotifier.ts
@@ -0,0 +1,30 @@
+
+
+import * as BluebirdPromise from "bluebird";
+
+import { IMailSender } from "./IMailSender";
+import { AbstractEmailNotifier } from "../notifiers/AbstractEmailNotifier";
+import { SmtpNotifierConfiguration } from "../configuration/schema/NotifierConfiguration";
+
+export class SmtpNotifier extends AbstractEmailNotifier {
+  private mailSender: IMailSender;
+  private sender: string;
+
+  constructor(options: SmtpNotifierConfiguration,
+    mailSender: IMailSender) {
+    super();
+    this.mailSender = mailSender;
+    this.sender = options.sender;
+  }
+
+  sendEmail(to: string, subject: string, content: string) {
+    const mailOptions = {
+      from: this.sender,
+      to: to,
+      subject: subject,
+      html: content
+    };
+    const that = this;
+    return this.mailSender.send(mailOptions);
+  }
+}
diff --git a/themes/triangles/server/src/lib/regulation/IRegulator.ts b/themes/triangles/server/src/lib/regulation/IRegulator.ts
new file mode 100644
index 00000000..c49425b2
--- /dev/null
+++ b/themes/triangles/server/src/lib/regulation/IRegulator.ts
@@ -0,0 +1,6 @@
+import BluebirdPromise = require("bluebird");
+
+export interface IRegulator {
+  mark(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void>;
+  regulate(userId: string): BluebirdPromise<void>;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/regulation/Regulator.spec.ts b/themes/triangles/server/src/lib/regulation/Regulator.spec.ts
new file mode 100644
index 00000000..f9c6e608
--- /dev/null
+++ b/themes/triangles/server/src/lib/regulation/Regulator.spec.ts
@@ -0,0 +1,186 @@
+
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+
+import { Regulator } from "./Regulator";
+import MockDate = require("mockdate");
+import exceptions = require("../Exceptions");
+import { UserDataStoreStub } from "../storage/UserDataStoreStub.spec";
+
+describe("regulation/Regulator", function () {
+  const USER1 = "USER1";
+  const USER2 = "USER2";
+  let userDataStoreStub: UserDataStoreStub;
+
+  beforeEach(function () {
+    userDataStoreStub = new UserDataStoreStub();
+    const dataStore: { [userId: string]: { userId: string, date: Date, isAuthenticationSuccessful: boolean }[] } = {
+      [USER1]: [],
+      [USER2]: []
+    };
+
+    userDataStoreStub.saveAuthenticationTraceStub.callsFake(function (userId, isAuthenticationSuccessful) {
+      dataStore[userId].unshift({
+        userId: userId,
+        date: new Date(),
+        isAuthenticationSuccessful: isAuthenticationSuccessful,
+      });
+      return BluebirdPromise.resolve();
+    });
+
+    userDataStoreStub.retrieveLatestAuthenticationTracesStub.callsFake(function (userId, count) {
+      const ret = (dataStore[userId].length <= count) ? dataStore[userId] : dataStore[userId].slice(0, 3);
+      return BluebirdPromise.resolve(ret);
+    });
+  });
+
+  afterEach(function () {
+    MockDate.reset();
+  });
+
+  function markAuthenticationAt(regulator: Regulator, user: string, time: string, success: boolean) {
+    MockDate.set(time);
+    return regulator.mark(user, success);
+  }
+
+  it("should mark 2 authentication and regulate (accept)", function () {
+    const regulator = new Regulator(userDataStoreStub, 3, 10, 10);
+
+    return regulator.mark(USER1, false)
+      .then(function () {
+        return regulator.mark(USER1, true);
+      })
+      .then(function () {
+        return regulator.regulate(USER1);
+      });
+  });
+
+  it("should mark 3 authentications and regulate (reject)", function () {
+    const regulator = new Regulator(userDataStoreStub, 3, 10, 10);
+
+    return regulator.mark(USER1, false)
+      .then(function () {
+        return regulator.mark(USER1, false);
+      })
+      .then(function () {
+        return regulator.mark(USER1, false);
+      })
+      .then(function () {
+        return regulator.regulate(USER1);
+      })
+      .then(function () { return BluebirdPromise.reject(new Error("should not be here!")); })
+      .catch(exceptions.AuthenticationRegulationError, function () {
+        return BluebirdPromise.resolve();
+      });
+  });
+
+  it("should mark 1 failed, 1 successful and 1 failed authentications within minimum time and regulate (accept)", function () {
+    const regulator = new Regulator(userDataStoreStub, 3, 60, 30);
+
+    return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:00", false)
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:10", true);
+      })
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:20", false);
+      })
+      .then(function () {
+        return regulator.regulate(USER1);
+      })
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:30", false);
+      })
+      .then(function () {
+        return regulator.regulate(USER1);
+      })
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:39", false);
+      })
+      .then(function () {
+        return regulator.regulate(USER1);
+      })
+      .then(function () {
+        return BluebirdPromise.reject(new Error("should not be here!"));
+      },
+      function () {
+        return BluebirdPromise.resolve();
+      });
+  });
+
+  it("should regulate user if number of failures is greater than 3 in allowed time lapse", function () {
+    function markAuthentications(regulator: Regulator, user: string) {
+      return markAuthenticationAt(regulator, user, "1/2/2000 00:00:00", false)
+        .then(function () {
+          return markAuthenticationAt(regulator, user, "1/2/2000 00:00:45", false);
+        })
+        .then(function () {
+          return markAuthenticationAt(regulator, user, "1/2/2000 00:01:05", false);
+        })
+        .then(function () {
+          return regulator.regulate(user);
+        });
+    }
+
+    const regulator1 = new Regulator(userDataStoreStub, 3, 60, 60);
+    const regulator2 = new Regulator(userDataStoreStub, 3, 2 * 60, 60);
+
+    const p1 = markAuthentications(regulator1, USER1);
+    const p2 = markAuthentications(regulator2, USER2);
+
+    return BluebirdPromise.join(p1, p2)
+      .then(function () {
+        return BluebirdPromise.reject(new Error("should not be here..."));
+      }, function () {
+        Assert(p1.isFulfilled());
+        Assert(p2.isRejected());
+      });
+  });
+
+  it("should user wait after regulation to authenticate again", function () {
+    function markAuthentications(regulator: Regulator, user: string) {
+      return markAuthenticationAt(regulator, user, "1/2/2000 00:00:00", false)
+        .then(function () {
+          return markAuthenticationAt(regulator, user, "1/2/2000 00:00:10", false);
+        })
+        .then(function () {
+          return markAuthenticationAt(regulator, user, "1/2/2000 00:00:15", false);
+        })
+        .then(function () {
+          return markAuthenticationAt(regulator, user, "1/2/2000 00:00:25", false);
+        })
+        .then(function () {
+          MockDate.set("1/2/2000 00:00:54");
+          return regulator.regulate(user);
+        })
+        .then(function () {
+          return BluebirdPromise.reject(new Error("should fail at this time"));
+        }, function () {
+          MockDate.set("1/2/2000 00:00:56");
+          return regulator.regulate(user);
+        });
+    }
+
+    const regulator = new Regulator(userDataStoreStub, 4, 30, 30);
+    return markAuthentications(regulator, USER1);
+  });
+
+  it("should disable regulation when max_retries is set to 0", function () {
+    const maxRetries = 0;
+    const regulator = new Regulator(userDataStoreStub, maxRetries, 60, 30);
+    return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:00", false)
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:10", false);
+      })
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:15", false);
+      })
+      .then(function () {
+        return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:25", false);
+      })
+      .then(function () {
+        MockDate.set("1/2/2000 00:00:26");
+        return regulator.regulate(USER1);
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/regulation/Regulator.ts b/themes/triangles/server/src/lib/regulation/Regulator.ts
new file mode 100644
index 00000000..1037a6a1
--- /dev/null
+++ b/themes/triangles/server/src/lib/regulation/Regulator.ts
@@ -0,0 +1,55 @@
+
+import * as BluebirdPromise from "bluebird";
+import exceptions = require("../Exceptions");
+import { IUserDataStore } from "../storage/IUserDataStore";
+import { AuthenticationTraceDocument } from "../storage/AuthenticationTraceDocument";
+import { IRegulator } from "./IRegulator";
+
+export class Regulator implements IRegulator {
+  private userDataStore: IUserDataStore;
+  private banTime: number;
+  private findTime: number;
+  private maxRetries: number;
+
+  constructor(userDataStore: any, maxRetries: number, findTime: number, banTime: number) {
+    this.userDataStore = userDataStore;
+    this.banTime = banTime;
+    this.findTime = findTime;
+    this.maxRetries = maxRetries;
+  }
+
+  // Mark authentication
+  mark(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
+    return this.userDataStore.saveAuthenticationTrace(userId, isAuthenticationSuccessful);
+  }
+
+  regulate(userId: string): BluebirdPromise<void> {
+    const that = this;
+
+    if (that.maxRetries <= 0) return BluebirdPromise.resolve();
+
+    return this.userDataStore.retrieveLatestAuthenticationTraces(userId, that.maxRetries)
+      .then((docs: AuthenticationTraceDocument[]) => {
+        // less than the max authorized number of authentication in time range, thus authorizing access
+        if (docs.length < that.maxRetries) return BluebirdPromise.resolve();
+
+        const numberOfFailedAuth = docs
+          .map(function (d: AuthenticationTraceDocument) { return d.isAuthenticationSuccessful == false ? 1 : 0; })
+          .reduce(function (acc, v) { return acc + v; }, 0);
+
+        if (numberOfFailedAuth < this.maxRetries) return BluebirdPromise.resolve();
+
+        const newestDocument = docs[0];
+        const oldestDocument = docs[that.maxRetries - 1];
+
+        const authenticationsTimeRangeInSeconds = (newestDocument.date.getTime() - oldestDocument.date.getTime()) / 1000;
+        const tooManyAuthInTimelapse = (authenticationsTimeRangeInSeconds < this.findTime);
+        const stillInBannedTimeRange = (new Date(new Date().getTime() - this.banTime * 1000) < newestDocument.date);
+
+        if (tooManyAuthInTimelapse && stillInBannedTimeRange)
+          throw new exceptions.AuthenticationRegulationError("Max number of authentication. Please retry in few minutes.");
+
+        return BluebirdPromise.resolve();
+      });
+  }
+}
diff --git a/themes/triangles/server/src/lib/regulation/RegulatorStub.spec.ts b/themes/triangles/server/src/lib/regulation/RegulatorStub.spec.ts
new file mode 100644
index 00000000..ca8a00fb
--- /dev/null
+++ b/themes/triangles/server/src/lib/regulation/RegulatorStub.spec.ts
@@ -0,0 +1,22 @@
+import Bluebird = require("bluebird");
+import Sinon = require("sinon");
+import { IRegulator } from "./IRegulator";
+
+
+export class RegulatorStub implements IRegulator {
+    markStub: Sinon.SinonStub;
+    regulateStub: Sinon.SinonStub;
+
+    constructor() {
+        this.markStub = Sinon.stub();
+        this.regulateStub = Sinon.stub();
+    }
+
+    mark(userId: string, isAuthenticationSuccessful: boolean): Bluebird<void>  {
+        return this.markStub(userId, isAuthenticationSuccessful);
+    }
+
+    regulate(userId: string): Bluebird<void> {
+        return this.regulateStub(userId);
+    }
+}
diff --git a/themes/triangles/server/src/lib/routes/error/401/get.spec.ts b/themes/triangles/server/src/lib/routes/error/401/get.spec.ts
new file mode 100644
index 00000000..9fdac9c3
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/error/401/get.spec.ts
@@ -0,0 +1,61 @@
+import Sinon = require("sinon");
+import Express = require("express");
+import Assert = require("assert");
+import Get401 from "./get";
+import { ServerVariables } from "../../../ServerVariables";
+import { ServerVariablesMockBuilder, ServerVariablesMock }
+  from "../../../ServerVariablesMockBuilder.spec";
+
+describe("routes/error/401/get", function () {
+  let vars: ServerVariables;
+  let mocks: ServerVariablesMock;
+  let req: any;
+  let res: any;
+  let renderSpy: Sinon.SinonSpy;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    vars = s.variables;
+    mocks = s.mocks;
+
+    renderSpy = Sinon.spy();
+    req = {
+      headers: {}
+    };
+    res = {
+      render: renderSpy
+    };
+  });
+
+  it("should set redirection url to the default redirection url", function () {
+    vars.config.default_redirection_url = "http://default-redirection";
+    return Get401(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/401", {
+          redirection_url: "http://default-redirection"
+        }));
+      });
+  });
+
+  it("should set redirection url to the referer", function () {
+    req.headers["referer"] = "http://redirection";
+    return Get401(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/401", {
+          redirection_url: "http://redirection"
+        }));
+      });
+  });
+
+  it("should render without redirecting the user", function () {
+    return Get401(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/401", {
+          redirection_url: undefined
+        }));
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/error/401/get.ts b/themes/triangles/server/src/lib/routes/error/401/get.ts
new file mode 100644
index 00000000..ca4a3963
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/error/401/get.ts
@@ -0,0 +1,15 @@
+
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import redirector from "../redirector";
+import { ServerVariables } from "../../../ServerVariables";
+
+export default function (vars: ServerVariables) {
+  return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
+    const redirectionUrl = redirector(req, vars);
+    res.render("errors/401", {
+      redirection_url: redirectionUrl
+    });
+    return BluebirdPromise.resolve();
+  };
+}
diff --git a/themes/triangles/server/src/lib/routes/error/403/get.spec.ts b/themes/triangles/server/src/lib/routes/error/403/get.spec.ts
new file mode 100644
index 00000000..22eb8485
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/error/403/get.spec.ts
@@ -0,0 +1,61 @@
+import Sinon = require("sinon");
+import Express = require("express");
+import Assert = require("assert");
+import Get403 from "./get";
+import { ServerVariables } from "../../../ServerVariables";
+import { ServerVariablesMockBuilder, ServerVariablesMock }
+  from "../../../ServerVariablesMockBuilder.spec";
+
+describe("routes/error/403/get", function () {
+  let vars: ServerVariables;
+  let mocks: ServerVariablesMock;
+  let req: any;
+  let res: any;
+  let renderSpy: Sinon.SinonSpy;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    vars = s.variables;
+    mocks = s.mocks;
+
+    renderSpy = Sinon.spy();
+    req = {
+      headers: {}
+    };
+    res = {
+      render: renderSpy
+    };
+  });
+
+  it("should set redirection url to the default redirection url", function () {
+    vars.config.default_redirection_url = "http://default-redirection";
+    return Get403(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/403", {
+          redirection_url: "http://default-redirection"
+        }));
+      });
+  });
+
+  it("should set redirection url to the referer", function () {
+    req.headers["referer"] = "http://redirection";
+    return Get403(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/403", {
+          redirection_url: "http://redirection"
+        }));
+      });
+  });
+
+  it("should render without redirecting the user", function () {
+    return Get403(vars)(req, res as any)
+      .then(function () {
+        Assert(renderSpy.calledOnce);
+        Assert(renderSpy.calledWithExactly("errors/403", {
+          redirection_url: undefined
+        }));
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/error/403/get.ts b/themes/triangles/server/src/lib/routes/error/403/get.ts
new file mode 100644
index 00000000..3ab0319e
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/error/403/get.ts
@@ -0,0 +1,15 @@
+
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import redirector from "../redirector";
+import { ServerVariables } from "../../../ServerVariables";
+
+export default function (vars: ServerVariables) {
+  return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
+    const redirectionUrl = redirector(req, vars);
+    res.render("errors/403", {
+      redirection_url: redirectionUrl
+    });
+    return BluebirdPromise.resolve();
+  };
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/error/404/get.spec.ts b/themes/triangles/server/src/lib/routes/error/404/get.spec.ts
new file mode 100644
index 00000000..73e4e6ce
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/error/404/get.spec.ts
@@ -0,0 +1,19 @@
+import Sinon = require("sinon");
+import Express = require("express");
+import Assert = require("assert");
+import Get404 from "./get";
+
+describe("routes/error/404/get", function () {
+  it("should render the page", function () {
+    const req = {} as Express.Request;
+    const res = {
+      render: Sinon.stub()
+    };
+
+    return Get404(req, res as any)
+      .then(function () {
+        Assert(res.render.calledOnce);
+        Assert(res.render.calledWith("errors/404"));
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/error/404/get.ts b/themes/triangles/server/src/lib/routes/error/404/get.ts
new file mode 100644
index 00000000..6693b6fc
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/error/404/get.ts
@@ -0,0 +1,8 @@
+
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+
+export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
+    res.render("errors/404");
+    return BluebirdPromise.resolve();
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/error/redirector.ts b/themes/triangles/server/src/lib/routes/error/redirector.ts
new file mode 100644
index 00000000..b1a3ccc1
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/error/redirector.ts
@@ -0,0 +1,13 @@
+import Express = require("express");
+import { ServerVariables } from "../../ServerVariables";
+
+export default function (req: Express.Request, vars: ServerVariables): string {
+  let redirectionUrl: string;
+
+  if (req.headers && req.headers["referer"])
+    redirectionUrl = "" + req.headers["referer"];
+  else if (vars.config.default_redirection_url)
+    redirectionUrl = vars.config.default_redirection_url;
+
+  return redirectionUrl;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/firstfactor/get.ts b/themes/triangles/server/src/lib/routes/firstfactor/get.ts
new file mode 100644
index 00000000..d94f656c
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/firstfactor/get.ts
@@ -0,0 +1,72 @@
+
+import express = require("express");
+import Endpoints = require("../../../../../shared/api");
+import BluebirdPromise = require("bluebird");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import Constants = require("../../../../../shared/constants");
+import Util = require("util");
+import { ServerVariables } from "../../ServerVariables";
+import { SafeRedirector } from "../../utils/SafeRedirection";
+import { Level } from "../../authentication/Level";
+
+function getRedirectParam(
+  req: express.Request) {
+  return req.query[Constants.REDIRECT_QUERY_PARAM] != "undefined"
+    ? req.query[Constants.REDIRECT_QUERY_PARAM]
+    : undefined;
+}
+
+function redirectToSecondFactorPage(
+  req: express.Request,
+  res: express.Response) {
+
+  const redirectUrl = getRedirectParam(req);
+  if (!redirectUrl)
+    res.redirect(Endpoints.SECOND_FACTOR_GET);
+  else
+    res.redirect(
+      Util.format("%s?%s=%s",
+        Endpoints.SECOND_FACTOR_GET,
+        Constants.REDIRECT_QUERY_PARAM,
+        redirectUrl));
+}
+
+function redirectToService(
+  req: express.Request,
+  res: express.Response,
+  redirector: SafeRedirector) {
+  const redirectUrl = getRedirectParam(req);
+  if (!redirectUrl) {
+    res.redirect(Endpoints.LOGGED_IN);
+  } else {
+    redirector.redirectOrElse(res, redirectUrl, Endpoints.LOGGED_IN);
+  }
+}
+
+function renderFirstFactor(
+  res: express.Response) {
+
+  res.render("firstfactor", {
+    first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
+    reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET
+  });
+}
+
+export default function (
+  vars: ServerVariables) {
+
+  const redirector = new SafeRedirector(vars.config.session.domain);
+  return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
+    return new BluebirdPromise(function (resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      if (authSession.authentication_level == Level.ONE_FACTOR) {
+        redirectToSecondFactorPage(req, res);
+      } else if (authSession.authentication_level == Level.TWO_FACTOR) {
+        redirectToService(req, res, redirector);
+      } else {
+        renderFirstFactor(res);
+      }
+      resolve();
+    });
+  };
+}
diff --git a/themes/triangles/server/src/lib/routes/firstfactor/post.spec.ts b/themes/triangles/server/src/lib/routes/firstfactor/post.spec.ts
new file mode 100644
index 00000000..e1d078cd
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/firstfactor/post.spec.ts
@@ -0,0 +1,136 @@
+
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+import FirstFactorPost = require("./post");
+import exceptions = require("../../Exceptions");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../types/AuthenticationSession";
+import Endpoints = require("../../../../../shared/api");
+import AuthenticationRegulatorMock = require("../../regulation/RegulatorStub.spec");
+import ExpressMock = require("../../stubs/express.spec");
+import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../ServerVariables";
+
+describe("routes/firstfactor/post", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let emails: string[];
+  let groups: string[];
+  let vars: ServerVariables;
+  let mocks: ServerVariablesMock;
+  let authSession: AuthenticationSession;
+
+  beforeEach(function () {
+    emails = ["test_ok@example.com"];
+    groups = ["group1", "group2" ];
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    mocks.authorizer.authorizationMock.returns(true);
+    mocks.regulator.regulateStub.returns(BluebirdPromise.resolve());
+    mocks.regulator.markStub.returns(BluebirdPromise.resolve());
+
+    req = {
+      originalUrl: "/api/firstfactor",
+      body: {
+        username: "username",
+        password: "password"
+      },
+      query: {
+        redirect: "http://redirect.url"
+      },
+      session: {
+        cookie: {}
+      },
+      headers: {
+        host: "home.example.com"
+      }
+    };
+
+    res = ExpressMock.ResponseMock();
+    authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
+  });
+
+  it("should reply with 204 if success", function () {
+    mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
+      .returns(BluebirdPromise.resolve({
+        emails: emails,
+        groups: groups
+      }));
+    return FirstFactorPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal("username", authSession.userid);
+        Assert(res.send.calledOnce);
+      });
+  });
+
+  describe("keep me logged in", () => {
+    beforeEach(() => {
+      mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
+        .returns(BluebirdPromise.resolve({
+          emails: emails,
+          groups: groups
+        }));
+      req.body.keepMeLoggedIn = "true";
+      return FirstFactorPost.default(vars)(req as any, res as any);
+    });
+
+    it("should set keep_me_logged_in session variable to true", function () {
+      Assert.equal(authSession.keep_me_logged_in, true);
+    });
+
+    it("should set cookie maxAge to one year", function () {
+      Assert.equal(req.session.cookie.maxAge, 365 * 24 * 60 * 60 * 1000);
+    });
+  });
+
+  it("should retrieve email from LDAP", function () {
+    mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
+      .returns(BluebirdPromise.resolve([{ mail: ["test@example.com"] }]));
+    return FirstFactorPost.default(vars)(req as any, res as any);
+  });
+
+  it("should set first email address as user session variable", function () {
+    const emails = ["test_ok@example.com"];
+    mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
+      .returns(BluebirdPromise.resolve({
+        emails: emails,
+        groups: groups
+      }));
+
+      return FirstFactorPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal("test_ok@example.com", authSession.email);
+      });
+  });
+
+  it("should return error message when LDAP authenticator throws", function () {
+    mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
+      .returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials")));
+
+    return FirstFactorPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal(res.status.getCall(0).args[0], 200);
+        Assert.equal(mocks.regulator.markStub.getCall(0).args[0], "username");
+        Assert.deepEqual(res.send.getCall(0).args[0], {
+          error: "Operation failed."
+        });
+      });
+  });
+
+  it("should return error message when regulator rejects authentication", function () {
+    const err = new exceptions.AuthenticationRegulationError("Authentication regulation...");
+    mocks.regulator.regulateStub.returns(BluebirdPromise.reject(err));
+    return FirstFactorPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal(res.status.getCall(0).args[0], 200);
+        Assert.deepEqual(res.send.getCall(0).args[0], {
+          error: "Operation failed."
+        });
+      });
+  });
+});
+
+
diff --git a/themes/triangles/server/src/lib/routes/firstfactor/post.ts b/themes/triangles/server/src/lib/routes/firstfactor/post.ts
new file mode 100644
index 00000000..565681d6
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/firstfactor/post.ts
@@ -0,0 +1,101 @@
+
+import Exceptions = require("../../Exceptions");
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import Endpoint = require("../../../../../shared/api");
+import ErrorReplies = require("../../ErrorReplies");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import Constants = require("../../../../../shared/constants");
+import UserMessages = require("../../../../../shared/UserMessages");
+import { ServerVariables } from "../../ServerVariables";
+import { AuthenticationSession } from "../../../../types/AuthenticationSession";
+import { GroupsAndEmails } from "../../authentication/backends/GroupsAndEmails";
+import { Level as AuthenticationLevel } from "../../authentication/Level";
+import { Level as AuthorizationLevel } from "../../authorization/Level";
+import { URLDecomposer } from "../../utils/URLDecomposer";
+
+export default function (vars: ServerVariables) {
+  return function (req: express.Request, res: express.Response)
+    : BluebirdPromise<void> {
+    const username: string = req.body.username;
+    const password: string = req.body.password;
+    const keepMeLoggedIn: boolean = req.body.keepMeLoggedIn &&
+      req.body.keepMeLoggedIn === "true";
+    let authSession: AuthenticationSession;
+
+    if (keepMeLoggedIn) {
+      // Stay connected for 1 year.
+      vars.logger.debug(req, "User requested to stay logged in for one year.");
+      req.session.cookie.maxAge = 365 * 24 * 60 * 60 * 1000;
+    }
+
+    return BluebirdPromise.resolve()
+      .then(function () {
+        if (!username || !password) {
+          return BluebirdPromise.reject(new Error("No username or password."));
+        }
+        vars.logger.info(req, "Starting authentication of user \"%s\"", username);
+        authSession = AuthenticationSessionHandler.get(req, vars.logger);
+        return vars.regulator.regulate(username);
+      })
+      .then(function () {
+        vars.logger.info(req, "No regulation applied.");
+        return vars.usersDatabase.checkUserPassword(username, password);
+      })
+      .then(function (groupsAndEmails: GroupsAndEmails) {
+        vars.logger.info(req,
+          "LDAP binding successful. Retrieved information about user are %s",
+          JSON.stringify(groupsAndEmails));
+        authSession.userid = username;
+        authSession.keep_me_logged_in = keepMeLoggedIn;
+        authSession.authentication_level = AuthenticationLevel.ONE_FACTOR;
+        const redirectUrl: string = req.query[Constants.REDIRECT_QUERY_PARAM] !== "undefined"
+          // Fuck, don't know why it is a string!
+          ? req.query[Constants.REDIRECT_QUERY_PARAM]
+          : "";
+
+        const emails: string[] = groupsAndEmails.emails;
+        const groups: string[] = groupsAndEmails.groups;
+        const decomposition = URLDecomposer.fromUrl(redirectUrl);
+        const authorizationLevel = (decomposition)
+          ? vars.authorizer.authorization(
+            {domain: decomposition.domain, resource: decomposition.path},
+            {user: username, groups: groups})
+          : AuthorizationLevel.TWO_FACTOR;
+
+        if (emails.length > 0)
+          authSession.email = emails[0];
+        authSession.groups = groups;
+
+        vars.logger.debug(req, "Mark successful authentication to regulator.");
+        vars.regulator.mark(username, true);
+
+        if (authorizationLevel <= AuthorizationLevel.ONE_FACTOR) {
+          let newRedirectionUrl: string = redirectUrl;
+          if (!newRedirectionUrl)
+            newRedirectionUrl = Endpoint.LOGGED_IN;
+          res.send({
+            redirect: newRedirectionUrl
+          });
+          vars.logger.debug(req, "Redirect to '%s'", redirectUrl);
+        }
+        else {
+          let newRedirectUrl = Endpoint.SECOND_FACTOR_GET;
+          if (redirectUrl) {
+            newRedirectUrl += "?" + Constants.REDIRECT_QUERY_PARAM + "="
+              + redirectUrl;
+          }
+          vars.logger.debug(req, "Redirect to '%s'", newRedirectUrl);
+          res.send({
+            redirect: newRedirectUrl
+          });
+        }
+        return BluebirdPromise.resolve();
+      })
+      .catch(Exceptions.LdapBindError, function (err: Error) {
+        vars.regulator.mark(username, false);
+        return ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.OPERATION_FAILED)(err);
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.OPERATION_FAILED));
+  };
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/loggedin/get.ts b/themes/triangles/server/src/lib/routes/loggedin/get.ts
new file mode 100644
index 00000000..283a041b
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/loggedin/get.ts
@@ -0,0 +1,23 @@
+import Express = require("express");
+import Endpoints = require("../../../../../shared/api");
+import BluebirdPromise = require("bluebird");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import { ServerVariables } from "../../ServerVariables";
+import ErrorReplies = require("../../ErrorReplies");
+
+export default function (vars: ServerVariables) {
+  function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
+    return new BluebirdPromise<void>(function (resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      res.render("already-logged-in", {
+        logout_endpoint: Endpoints.LOGOUT_GET,
+        username: authSession.userid,
+        redirection_url: vars.config.default_redirection_url
+      });
+      resolve();
+    })
+      .catch(ErrorReplies.replyWithError401(req, res, vars.logger));
+  }
+
+  return handler;
+}
diff --git a/themes/triangles/server/src/lib/routes/logout/get.ts b/themes/triangles/server/src/lib/routes/logout/get.ts
new file mode 100644
index 00000000..4d511214
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/logout/get.ts
@@ -0,0 +1,20 @@
+
+import express = require("express");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import Constants = require("../../../../../shared/constants");
+import { ServerVariables } from "../../ServerVariables";
+
+function getRedirectParam(req: express.Request) {
+  return req.query[Constants.REDIRECT_QUERY_PARAM] != "undefined"
+    ? req.query[Constants.REDIRECT_QUERY_PARAM]
+    : undefined;
+}
+
+export default function (vars: ServerVariables) {
+  return function(req: express.Request, res: express.Response) {
+    const redirect_param = getRedirectParam(req);
+    const redirect_url = redirect_param || "/";
+    AuthenticationSessionHandler.reset(req);
+    res.redirect(redirect_url);
+  };
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/password-reset/constants.ts b/themes/triangles/server/src/lib/routes/password-reset/constants.ts
new file mode 100644
index 00000000..5c639e92
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/password-reset/constants.ts
@@ -0,0 +1,2 @@
+
+export const CHALLENGE = "reset-password";
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/password-reset/form/post.spec.ts b/themes/triangles/server/src/lib/routes/password-reset/form/post.spec.ts
new file mode 100644
index 00000000..ed029c90
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/password-reset/form/post.spec.ts
@@ -0,0 +1,122 @@
+
+import PasswordResetFormPost = require("./post");
+import { AuthenticationSessionHandler } from "../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
+import { UserDataStore } from "../../../storage/UserDataStore";
+import Sinon = require("sinon");
+import Assert = require("assert");
+import BluebirdPromise = require("bluebird");
+import ExpressMock = require("../../../stubs/express.spec");
+import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../ServerVariables";
+import { Level } from "../../../authentication/Level";
+
+describe("routes/password-reset/form/post", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let vars: ServerVariables;
+  let mocks: ServerVariablesMock;
+  let authSession: AuthenticationSession;
+
+  beforeEach(function () {
+    req = {
+      originalUrl: "/api/password-reset",
+      body: {
+        userid: "user"
+      },
+      session: {},
+      headers: {
+        host: "localhost"
+      }
+    };
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
+
+    mocks.config.authentication_backend.ldap = {
+      url: "ldap://ldapjs",
+      mail_attribute: "mail",
+      user: "user",
+      password: "password",
+      additional_users_dn: "ou=users",
+      additional_groups_dn: "ou=groups",
+      base_dn: "dc=example,dc=com",
+      users_filter: "user",
+      group_name_attribute: "cn",
+      groups_filter: "groups"
+    };
+
+    res = ExpressMock.ResponseMock();
+    authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
+    authSession.userid = "user";
+    authSession.email = "user@example.com";
+    authSession.authentication_level = Level.ONE_FACTOR;
+  });
+
+  describe("test reset password post", () => {
+    it("should update the password and reset auth_session for reauthentication", function () {
+      req.body = {};
+      req.body.password = "new-password";
+
+      mocks.usersDatabase.updatePasswordStub.returns(BluebirdPromise.resolve());
+
+      authSession.identity_check = {
+        userid: "user",
+        challenge: "reset-password"
+      };
+      return PasswordResetFormPost.default(vars)(req as any, res as any)
+        .then(function () {
+          return AuthenticationSessionHandler.get(req as any, vars.logger);
+        }).then(function (_authSession) {
+          Assert.equal(res.status.getCall(0).args[0], 204);
+          Assert.equal(_authSession.authentication_level, Level.NOT_AUTHENTICATED);
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail if identity_challenge does not exist", function () {
+      authSession.identity_check = {
+        userid: "user",
+        challenge: undefined
+      };
+      return PasswordResetFormPost.default(vars)(req as any, res as any)
+        .then(function () {
+          Assert.equal(res.status.getCall(0).args[0], 200);
+          Assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "An error occurred during password reset. Your password has not been changed."
+          });
+        });
+    });
+
+    it("should fail when ldap fails", function () {
+      req.body = {};
+      req.body.password = "new-password";
+
+      mocks.usersDatabase.updatePasswordStub
+        .returns(BluebirdPromise.reject("Internal error with LDAP"));
+
+      authSession.identity_check = {
+        challenge: "reset-password",
+        userid: "user"
+      };
+      return PasswordResetFormPost.default(vars)(req as any, res as any)
+        .then(function () {
+          Assert.equal(res.status.getCall(0).args[0], 200);
+          Assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "An error occurred during password reset. Your password has not been changed."
+          });
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+});
diff --git a/themes/triangles/server/src/lib/routes/password-reset/form/post.ts b/themes/triangles/server/src/lib/routes/password-reset/form/post.ts
new file mode 100644
index 00000000..fccd7471
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/password-reset/form/post.ts
@@ -0,0 +1,50 @@
+
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import objectPath = require("object-path");
+import exceptions = require("../../../Exceptions");
+import { AuthenticationSessionHandler } from "../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
+import ErrorReplies = require("../../../ErrorReplies");
+import UserMessages = require("../../../../../../shared/UserMessages");
+import { ServerVariables } from "../../../ServerVariables";
+
+import Constants = require("./../constants");
+
+export default function (vars: ServerVariables) {
+  return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    const newPassword = objectPath.get<express.Request, string>(req, "body.password");
+
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      if (!authSession.identity_check) {
+        reject(new Error("No identity check initiated"));
+        return;
+      }
+
+      vars.logger.info(req, "User %s wants to reset his/her password.",
+        authSession.identity_check.userid);
+      vars.logger.debug(req, "Challenge %s", authSession.identity_check.challenge);
+
+      if (authSession.identity_check.challenge != Constants.CHALLENGE) {
+        reject(new Error("Bad challenge."));
+        return;
+      }
+      resolve();
+    })
+      .then(function () {
+        return vars.usersDatabase.updatePassword(authSession.identity_check.userid, newPassword);
+      })
+      .then(function () {
+        vars.logger.info(req, "Password reset for user '%s'",
+          authSession.identity_check.userid);
+        AuthenticationSessionHandler.reset(req);
+        res.status(204);
+        res.send();
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.RESET_PASSWORD_FAILED));
+  };
+}
diff --git a/themes/triangles/server/src/lib/routes/password-reset/identity/PasswordResetHandler.spec.ts b/themes/triangles/server/src/lib/routes/password-reset/identity/PasswordResetHandler.spec.ts
new file mode 100644
index 00000000..ac6a4175
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/password-reset/identity/PasswordResetHandler.spec.ts
@@ -0,0 +1,92 @@
+
+import PasswordResetHandler
+  from "./PasswordResetHandler";
+import { UserDataStore } from "../../../storage/UserDataStore";
+import Sinon = require("sinon");
+import winston = require("winston");
+import assert = require("assert");
+import BluebirdPromise = require("bluebird");
+import ExpressMock = require("../../../stubs/express.spec");
+import { ServerVariablesMock, ServerVariablesMockBuilder }
+  from "../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../ServerVariables";
+
+describe("routes/password-reset/identity/PasswordResetHandler", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    req = {
+      originalUrl: "/non-api/xxx",
+      query: {
+        userid: "user"
+      },
+      session: {
+        auth: {
+          userid: "user",
+          email: "user@example.com",
+          first_factor: true,
+          second_factor: false
+        }
+      },
+      headers: {
+        host: "localhost"
+      }
+    };
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    mocks.userDataStore.saveU2FRegistrationStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.produceIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.consumeIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve({}));
+    res = ExpressMock.ResponseMock();
+  });
+
+  describe("test reset password identity pre check", () => {
+    it("should fail when no userid is provided", function () {
+      req.query.userid = undefined;
+      const handler = new PasswordResetHandler(vars.logger,
+        vars.usersDatabase);
+      return handler.preValidationInit(req as any)
+        .then(function () {
+          return BluebirdPromise.reject("It should fail");
+        })
+        .catch(function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail if ldap fail", function () {
+      mocks.usersDatabase.getEmailsStub
+        .returns(BluebirdPromise.reject("Internal error"));
+      new PasswordResetHandler(vars.logger, vars.usersDatabase)
+        .preValidationInit(req as any)
+        .then(function () {
+          return BluebirdPromise.reject(new Error("should not be here"));
+        },
+        function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should returns identity when ldap replies", function () {
+      mocks.usersDatabase.getEmailsStub
+        .returns(BluebirdPromise.resolve(["test@example.com"]));
+      return new PasswordResetHandler(vars.logger, vars.usersDatabase)
+        .preValidationInit(req as any);
+    });
+  });
+});
diff --git a/themes/triangles/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts b/themes/triangles/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts
new file mode 100644
index 00000000..42ae92cd
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts
@@ -0,0 +1,69 @@
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import objectPath = require("object-path");
+
+import exceptions = require("../../../Exceptions");
+import { Identity } from "../../../../../types/Identity";
+import { IdentityValidable } from "../../../IdentityValidable";
+import { PRE_VALIDATION_TEMPLATE } from "../../../IdentityCheckPreValidationTemplate";
+import Constants = require("../constants");
+import { IRequestLogger } from "../../../logging/IRequestLogger";
+import { IUsersDatabase } from "../../../authentication/backends/IUsersDatabase";
+
+export const TEMPLATE_NAME = "password-reset-form";
+
+export default class PasswordResetHandler implements IdentityValidable {
+  private logger: IRequestLogger;
+  private usersDatabase: IUsersDatabase;
+
+  constructor(logger: IRequestLogger, usersDatabase: IUsersDatabase) {
+    this.logger = logger;
+    this.usersDatabase = usersDatabase;
+  }
+
+  challenge(): string {
+    return Constants.CHALLENGE;
+  }
+
+  preValidationInit(req: express.Request): BluebirdPromise<Identity> {
+    const that = this;
+    const userid: string =
+      objectPath.get<express.Request, string>(req, "query.userid");
+    return BluebirdPromise.resolve()
+      .then(function () {
+        that.logger.debug(req, "User '%s' requested a password reset", userid);
+        if (!userid)
+          return BluebirdPromise.reject(
+            new exceptions.AccessDeniedError("No user id provided"));
+
+        return that.usersDatabase.getEmails(userid);
+      })
+      .then(function (emails: string[]) {
+        if (!emails && emails.length <= 0) throw new Error("No email found");
+        const identity = {
+          email: emails[0],
+          userid: userid
+        };
+        return BluebirdPromise.resolve(identity);
+      })
+      .catch(function (err: Error) {
+        return BluebirdPromise.reject(new exceptions.IdentityError(err.message));
+      });
+  }
+
+  preValidationResponse(req: express.Request, res: express.Response) {
+    res.render(PRE_VALIDATION_TEMPLATE);
+  }
+
+  postValidationInit(req: express.Request) {
+    return BluebirdPromise.resolve();
+  }
+
+  postValidationResponse(req: express.Request, res: express.Response) {
+    res.render(TEMPLATE_NAME);
+  }
+
+  mailSubject(): string {
+    return "Reset your password";
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/password-reset/request/get.ts b/themes/triangles/server/src/lib/routes/password-reset/request/get.ts
new file mode 100644
index 00000000..8f3ae2b4
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/password-reset/request/get.ts
@@ -0,0 +1,13 @@
+
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import objectPath = require("object-path");
+import exceptions = require("../../../Exceptions");
+
+import Constants = require("./../constants");
+
+const TEMPLATE_NAME = "password-reset-request";
+
+export default function (req: express.Request, res: express.Response) {
+    res.render(TEMPLATE_NAME);
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/get.spec.ts b/themes/triangles/server/src/lib/routes/secondfactor/get.spec.ts
new file mode 100644
index 00000000..6c77e1f6
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/get.spec.ts
@@ -0,0 +1,44 @@
+import SecondFactorGet from "./get";
+import { ServerVariablesMockBuilder, ServerVariablesMock }
+  from "../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../ServerVariables";
+import Sinon = require("sinon");
+import ExpressMock = require("../../stubs/express.spec");
+import Assert = require("assert");
+import Endpoints = require("../../../../../shared/api");
+import BluebirdPromise = require("bluebird");
+
+describe("routes/secondfactor/get", function () {
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req = ExpressMock.RequestMock();
+    res = ExpressMock.ResponseMock();
+
+    req.session = {
+      auth: {
+        userid: "user",
+        first_factor: true,
+        second_factor: false
+      }
+    };
+  });
+
+  describe("test rendering", function () {
+    it("should render second factor page", function () {
+      req.session.auth.second_factor = false;
+      return SecondFactorGet(vars)(req as any, res as any)
+        .then(function () {
+          Assert(res.render.calledWith("secondfactor"));
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/get.ts b/themes/triangles/server/src/lib/routes/secondfactor/get.ts
new file mode 100644
index 00000000..9f6deb4c
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/get.ts
@@ -0,0 +1,28 @@
+
+import Express = require("express");
+import Endpoints = require("../../../../../shared/api");
+import BluebirdPromise = require("bluebird");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import { ServerVariables } from "../../ServerVariables";
+
+const TEMPLATE_NAME = "secondfactor";
+
+export default function (vars: ServerVariables) {
+  function handler(req: Express.Request, res: Express.Response)
+    : BluebirdPromise<void> {
+
+    return new BluebirdPromise(function (resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, vars.logger);
+
+      res.render(TEMPLATE_NAME, {
+        username: authSession.userid,
+        totp_identity_start_endpoint:
+        Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
+        u2f_identity_start_endpoint:
+        Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET
+      });
+      resolve();
+    });
+  }
+  return handler;
+}
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/redirect.spec.ts b/themes/triangles/server/src/lib/routes/secondfactor/redirect.spec.ts
new file mode 100644
index 00000000..ea66e6dc
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/redirect.spec.ts
@@ -0,0 +1,41 @@
+import Redirect from "./redirect";
+import ExpressMock = require("../../stubs/express.spec");
+import { ServerVariablesMockBuilder, ServerVariablesMock }
+from "../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../ServerVariables";
+import Assert = require("assert");
+
+describe("routes/secondfactor/redirect", function() {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req = ExpressMock.RequestMock();
+    res = ExpressMock.ResponseMock();
+  });
+
+  it("should redirect to default_redirection_url", function() {
+    vars.config.default_redirection_url = "http://default_redirection_url";
+    Redirect(vars)(req as any, res as any)
+    .then(function() {
+      Assert(res.json.calledWith({
+        redirect: "http://default_redirection_url"
+      }));
+    });
+  });
+
+  it("should redirect to /", function() {
+    Redirect(vars)(req as any, res as any)
+    .then(function() {
+      Assert(res.json.calledWith({
+        redirect: "/"
+      }));
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/redirect.ts b/themes/triangles/server/src/lib/routes/secondfactor/redirect.ts
new file mode 100644
index 00000000..5d84d9eb
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/redirect.ts
@@ -0,0 +1,30 @@
+
+import express = require("express");
+import objectPath = require("object-path");
+import Endpoints = require("../../../../../shared/api");
+import { ServerVariables } from "../../ServerVariables";
+import BluebirdPromise = require("bluebird");
+import ErrorReplies = require("../../ErrorReplies");
+import UserMessages = require("../../../../../shared/UserMessages");
+import { RedirectionMessage } from "../../../../../shared/RedirectionMessage";
+import Constants = require("../../../../../shared/constants");
+
+export default function (vars: ServerVariables) {
+  return function (req: express.Request, res: express.Response)
+    : BluebirdPromise<void> {
+
+    return new BluebirdPromise<void>(function (resolve, reject) {
+      let redirectUrl: string = "/";
+      if (vars.config.default_redirection_url) {
+        redirectUrl = vars.config.default_redirection_url;
+      }
+      vars.logger.debug(req, "Request redirection to \"%s\".", redirectUrl);
+      res.json({
+        redirect: redirectUrl
+      } as RedirectionMessage);
+      return resolve();
+    })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  };
+}
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/totp/constants.ts b/themes/triangles/server/src/lib/routes/secondfactor/totp/constants.ts
new file mode 100644
index 00000000..7b5a1dcf
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/totp/constants.ts
@@ -0,0 +1,4 @@
+
+export const CHALLENGE = "totp-register";
+export const TEMPLATE_NAME = "totp-register";
+
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.spec.ts b/themes/triangles/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.spec.ts
new file mode 100644
index 00000000..78b8ea3e
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.spec.ts
@@ -0,0 +1,116 @@
+import Sinon = require("sinon");
+import RegistrationHandler from "./RegistrationHandler";
+import { Identity } from "../../../../../../types/Identity";
+import { UserDataStore } from "../../../../storage/UserDataStore";
+import BluebirdPromise = require("bluebird");
+import ExpressMock = require("../../../../stubs/express.spec");
+import { ServerVariablesMock, ServerVariablesMockBuilder }
+  from "../../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../../ServerVariables";
+import Assert = require("assert");
+
+describe("routes/secondfactor/totp/identity/RegistrationHandler", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req = ExpressMock.RequestMock();
+    req.session = {
+      auth: {
+        userid: "user",
+        email: "user@example.com",
+        first_factor: true,
+        second_factor: false,
+        identity_check: {
+          userid: "user",
+          challenge: "totp-register"
+        }
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    mocks.userDataStore.saveU2FRegistrationStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.produceIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.consumeIdentityValidationTokenStub
+      .returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.saveTOTPSecretStub
+      .returns(BluebirdPromise.resolve({}));
+
+    res = ExpressMock.ResponseMock();
+  });
+
+  describe("test totp registration pre validation", function () {
+    it("should fail if first_factor has not been passed", function () {
+      req.session.auth.first_factor = false;
+      return new RegistrationHandler(vars.logger, vars.userDataStore,
+        vars.totpHandler, vars.config.totp)
+        .preValidationInit(req as any)
+        .then(function () {
+          return BluebirdPromise.reject(new Error("It should fail"));
+        })
+        .catch(function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail if userid is missing", function (done) {
+      req.session.auth.first_factor = false;
+      req.session.auth.userid = undefined;
+
+      new RegistrationHandler(vars.logger, vars.userDataStore, vars.totpHandler,
+        vars.config.totp)
+        .preValidationInit(req as any)
+        .catch(function (err: Error) {
+          done();
+        });
+    });
+
+    it("should fail if email is missing", function (done) {
+      req.session.auth.first_factor = false;
+      req.session.auth.email = undefined;
+
+      new RegistrationHandler(vars.logger, vars.userDataStore, vars.totpHandler,
+        vars.config.totp)
+        .preValidationInit(req as any)
+        .catch(function (err: Error) {
+          done();
+        });
+    });
+
+    it("should succeed if first factor passed, userid and email are provided",
+      function () {
+        return new RegistrationHandler(vars.logger, vars.userDataStore,
+          vars.totpHandler, vars.config.totp)
+          .preValidationInit(req as any);
+      });
+  });
+
+  describe("test totp registration post validation", function () {
+    it("should generate a secret using userId as label and issuer defined in config", function () {
+      vars.config.totp = {
+        issuer: "issuer"
+      };
+      return new RegistrationHandler(vars.logger, vars.userDataStore,
+        vars.totpHandler, vars.config.totp)
+        .postValidationResponse(req as any, res as any)
+        .then(function() {
+          Assert(mocks.totpHandler.generateStub.calledWithExactly("user", "issuer"));
+        });
+    });
+  });
+});
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts b/themes/triangles/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts
new file mode 100644
index 00000000..b39b6d04
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts
@@ -0,0 +1,112 @@
+
+import express = require("express");
+import BluebirdPromise = require("bluebird");
+import objectPath = require("object-path");
+
+import { Identity } from "../../../../../../types/Identity";
+import { IdentityValidable } from "../../../../IdentityValidable";
+import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationTemplate";
+import Constants = require("../constants");
+import Endpoints = require("../../../../../../../shared/api");
+import ErrorReplies = require("../../../../ErrorReplies");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import FirstFactorValidator = require("../../../../FirstFactorValidator");
+import { IRequestLogger } from "../../../../logging/IRequestLogger";
+import { IUserDataStore } from "../../../../storage/IUserDataStore";
+import { ITotpHandler } from "../../../../authentication/totp/ITotpHandler";
+import { TOTPSecret } from "../../../../../../types/TOTPSecret";
+import { TotpConfiguration } from "../../../../configuration/schema/TotpConfiguration";
+
+
+export default class RegistrationHandler implements IdentityValidable {
+  private logger: IRequestLogger;
+  private userDataStore: IUserDataStore;
+  private totp: ITotpHandler;
+  private configuration: TotpConfiguration;
+
+  constructor(logger: IRequestLogger,
+    userDataStore: IUserDataStore,
+    totp: ITotpHandler, configuration: TotpConfiguration) {
+    this.logger = logger;
+    this.userDataStore = userDataStore;
+    this.totp = totp;
+    this.configuration = configuration;
+  }
+
+  challenge(): string {
+    return Constants.CHALLENGE;
+  }
+
+  private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
+    const that = this;
+    return new BluebirdPromise(function (resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, that.logger);
+      const userid = authSession.userid;
+      const email = authSession.email;
+
+      if (!(userid && email)) {
+        return reject(new Error("User ID or email is missing"));
+      }
+
+      const identity = {
+        email: email,
+        userid: userid
+      };
+      return resolve(identity);
+    });
+  }
+
+  preValidationInit(req: express.Request): BluebirdPromise<Identity> {
+    const that = this;
+    return FirstFactorValidator.validate(req, this.logger)
+      .then(function () {
+        return that.retrieveIdentity(req);
+      });
+  }
+
+  preValidationResponse(req: express.Request, res: express.Response) {
+    res.render(PRE_VALIDATION_TEMPLATE);
+  }
+
+  postValidationInit(req: express.Request) {
+    return FirstFactorValidator.validate(req, this.logger);
+  }
+
+  postValidationResponse(req: express.Request, res: express.Response)
+    : BluebirdPromise<void> {
+    const that = this;
+    let secret: TOTPSecret;
+    let userId: string;
+    return new BluebirdPromise(function (resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, that.logger);
+      userId = authSession.userid;
+
+      if (authSession.identity_check.challenge != Constants.CHALLENGE
+        || !userId)
+        return reject(new Error("Bad challenge."));
+
+      resolve();
+    })
+      .then(function () {
+        secret = that.totp.generate(userId,
+          that.configuration.issuer);
+        that.logger.debug(req, "Save the TOTP secret in DB");
+        return that.userDataStore.saveTOTPSecret(userId, secret);
+      })
+      .then(function () {
+        AuthenticationSessionHandler.reset(req);
+
+        res.render(Constants.TEMPLATE_NAME, {
+          base32_secret: secret.base32,
+          otpauth_url: secret.otpauth_url,
+          login_endpoint: Endpoints.FIRST_FACTOR_GET
+        });
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, that.logger, UserMessages.OPERATION_FAILED));
+  }
+
+  mailSubject(): string {
+    return "Set up Authelia's one-time password";
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/totp/sign/post.spec.ts b/themes/triangles/server/src/lib/routes/secondfactor/totp/sign/post.spec.ts
new file mode 100644
index 00000000..70a20d39
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/totp/sign/post.spec.ts
@@ -0,0 +1,76 @@
+
+import BluebirdPromise = require("bluebird");
+import Sinon = require("sinon");
+import Assert = require("assert");
+import Exceptions = require("../../../../Exceptions");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+import SignPost = require("./post");
+import { ServerVariables } from "../../../../ServerVariables";
+
+import ExpressMock = require("../../../../stubs/express.spec");
+import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
+import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec";
+import { Level } from "../../../../authentication/Level";
+
+describe("routes/secondfactor/totp/sign/post", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let authSession: AuthenticationSession;
+  let vars: ServerVariables;
+  let mocks: ServerVariablesMock;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    vars = s.variables;
+    mocks = s.mocks;
+    const app_get = Sinon.stub();
+    req = {
+      originalUrl: "/api/totp-register",
+      app: {},
+      body: {
+        token: "abc"
+      },
+      session: {},
+      query: {
+        redirect: "http://redirect"
+      }
+    };
+    res = ExpressMock.ResponseMock();
+
+    const doc = {
+      userid: "user",
+      secret: {
+        base32: "ABCDEF"
+      }
+    };
+    mocks.userDataStore.retrieveTOTPSecretStub.returns(BluebirdPromise.resolve(doc));
+    authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
+    authSession.userid = "user";
+    authSession.authentication_level = Level.ONE_FACTOR;
+  });
+
+
+  it("should send status code 200 when totp is valid", function () {
+    mocks.totpHandler.validateStub.returns(true);
+    return SignPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal(authSession.authentication_level, Level.TWO_FACTOR);
+        return BluebirdPromise.resolve();
+      });
+  });
+
+  it("should send error message when totp is not valid", function () {
+    mocks.totpHandler.validateStub.returns(false);
+    return SignPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.notEqual(authSession.authentication_level, Level.TWO_FACTOR);
+        Assert.equal(res.status.getCall(0).args[0], 200);
+        Assert.deepEqual(res.send.getCall(0).args[0], {
+          error: "Operation failed."
+        });
+        return BluebirdPromise.resolve();
+      });
+  });
+});
+
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/totp/sign/post.ts b/themes/triangles/server/src/lib/routes/secondfactor/totp/sign/post.ts
new file mode 100644
index 00000000..34a276d1
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/totp/sign/post.ts
@@ -0,0 +1,42 @@
+import Bluebird = require("bluebird");
+import Express = require("express");
+
+import { TOTPSecretDocument } from "../../../../storage/TOTPSecretDocument";
+import Endpoints = require("../../../../../../../shared/api");
+import Redirect from "../../redirect";
+import ErrorReplies = require("../../../../ErrorReplies");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import { ServerVariables } from "../../../../ServerVariables";
+import { Level } from "../../../../authentication/Level";
+
+const UNAUTHORIZED_MESSAGE = "Unauthorized access";
+
+export default function (vars: ServerVariables) {
+  function handler(req: Express.Request, res: Express.Response): Bluebird<void> {
+    let authSession: AuthenticationSession;
+    const token = req.body.token;
+
+    return new Bluebird(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      vars.logger.info(req, "Initiate TOTP validation for user \"%s\".", authSession.userid);
+      resolve();
+    })
+      .then(function () {
+        return vars.userDataStore.retrieveTOTPSecret(authSession.userid);
+      })
+      .then(function (doc: TOTPSecretDocument) {
+        if (!vars.totpHandler.validate(token, doc.secret.base32))
+          return Bluebird.reject(new Error("Invalid TOTP token."));
+
+        vars.logger.debug(req, "TOTP validation succeeded.");
+        authSession.authentication_level = Level.TWO_FACTOR;
+        Redirect(vars)(req, res);
+        return Bluebird.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  }
+  return handler;
+}
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/u2f/U2FCommon.ts b/themes/triangles/server/src/lib/routes/secondfactor/u2f/U2FCommon.ts
new file mode 100644
index 00000000..7f16c0ee
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/u2f/U2FCommon.ts
@@ -0,0 +1,11 @@
+
+import util = require("util");
+import express = require("express");
+
+function extract_app_id(req: express.Request): string {
+  return util.format("https://%s", req.headers.host);
+}
+
+export = {
+  extract_app_id: extract_app_id
+};
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.spec.ts b/themes/triangles/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.spec.ts
new file mode 100644
index 00000000..a54bfbfe
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.spec.ts
@@ -0,0 +1,96 @@
+import Sinon = require("sinon");
+import Assert = require("assert");
+import BluebirdPromise = require("bluebird");
+
+import { Identity } from "../../../../../../types/Identity";
+import RegistrationHandler from "./RegistrationHandler";
+import ExpressMock = require("../../../../stubs/express.spec");
+import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
+import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../../ServerVariables";
+
+describe("routes/secondfactor/u2f/identity/RegistrationHandler", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req = ExpressMock.RequestMock();
+    req.app = {};
+    req.session = {
+      auth: {
+        userid: "user",
+        email: "user@example.com",
+        first_factor: true,
+        second_factor: false
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
+
+    res = ExpressMock.ResponseMock();
+    res.send = Sinon.spy();
+    res.json = Sinon.spy();
+    res.status = Sinon.spy();
+  });
+
+  describe("test u2f registration check", test_registration_check);
+
+  function test_registration_check() {
+    it("should fail if first_factor has not been passed", function () {
+      req.session.auth.first_factor = false;
+      return new RegistrationHandler(vars.logger).preValidationInit(req as any)
+        .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
+        .catch(function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail if userid is missing", function () {
+      req.session.auth.first_factor = false;
+      req.session.auth.userid = undefined;
+
+      return new RegistrationHandler(vars.logger).preValidationInit(req as any)
+        .then(function () {
+          return BluebirdPromise.reject(new Error("should not be here"));
+        },
+        function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should fail if email is missing", function () {
+      req.session.auth.first_factor = false;
+      req.session.auth.email = undefined;
+
+      return new RegistrationHandler(vars.logger).preValidationInit(req as any)
+        .then(function () {
+          return BluebirdPromise.reject(new Error("should not be here"));
+        },
+        function (err: Error) {
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should succeed if first factor passed, userid and email are provided", function () {
+      req.session.auth.first_factor = true;
+      req.session.auth.email = "admin@example.com";
+      req.session.auth.userid = "user";
+      return new RegistrationHandler(vars.logger).preValidationInit(req as any);
+    });
+  }
+});
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts b/themes/triangles/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts
new file mode 100644
index 00000000..bc4713c7
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts
@@ -0,0 +1,73 @@
+
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import objectPath = require("object-path");
+
+import { IdentityValidable } from "../../../../IdentityValidable";
+import { Identity } from "../../../../../../types/Identity";
+import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationTemplate";
+import FirstFactorValidator = require("../../../../FirstFactorValidator");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import { IRequestLogger } from "../../../../logging/IRequestLogger";
+
+const CHALLENGE = "u2f-register";
+const MAIL_SUBJECT = "Register your security key with Authelia";
+
+const POST_VALIDATION_TEMPLATE_NAME = "u2f-register";
+
+
+export default class RegistrationHandler implements IdentityValidable {
+  private logger: IRequestLogger;
+
+  constructor(logger: IRequestLogger) {
+    this.logger = logger;
+  }
+
+  challenge(): string {
+    return CHALLENGE;
+  }
+
+  private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
+    const that = this;
+    return new BluebirdPromise(function(resolve, reject) {
+      const authSession = AuthenticationSessionHandler.get(req, that.logger);
+      const userid = authSession.userid;
+      const email = authSession.email;
+
+      if (!(userid && email)) {
+        return reject(new Error("User ID or email is missing"));
+      }
+
+      const identity = {
+        email: email,
+        userid: userid
+      };
+      return resolve(identity);
+    });
+  }
+
+  preValidationInit(req: express.Request): BluebirdPromise<Identity> {
+    const that = this;
+    return FirstFactorValidator.validate(req, this.logger)
+      .then(function () {
+        return that.retrieveIdentity(req);
+      });
+  }
+
+  preValidationResponse(req: express.Request, res: express.Response) {
+    res.render(PRE_VALIDATION_TEMPLATE);
+  }
+
+  postValidationInit(req: express.Request) {
+    return FirstFactorValidator.validate(req, this.logger);
+  }
+
+  postValidationResponse(req: express.Request, res: express.Response) {
+    res.render(POST_VALIDATION_TEMPLATE_NAME);
+  }
+
+  mailSubject(): string {
+    return MAIL_SUBJECT;
+  }
+}
+
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/u2f/register/post.spec.ts b/themes/triangles/server/src/lib/routes/secondfactor/u2f/register/post.spec.ts
new file mode 100644
index 00000000..de3347a2
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/u2f/register/post.spec.ts
@@ -0,0 +1,146 @@
+
+import sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import assert = require("assert");
+import U2FRegisterPost = require("./post");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+import ExpressMock = require("../../../../stubs/express.spec");
+import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
+import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../../ServerVariables";
+
+
+describe("routes/secondfactor/u2f/register/post", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+  let authSession: AuthenticationSession;
+
+  beforeEach(function () {
+    req = ExpressMock.RequestMock();
+    req.originalUrl = "/api/xxxx";
+    req.app = {};
+    req.session = {
+      auth: {
+        userid: "user",
+        first_factor: true,
+        second_factor: false,
+        identity_check: {
+          challenge: "u2f-register",
+          userid: "user"
+        }
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+
+    res = ExpressMock.ResponseMock();
+    res.send = sinon.spy();
+    res.json = sinon.spy();
+    res.status = sinon.spy();
+
+    authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
+  });
+
+  describe("test registration", test_registration);
+
+
+  function test_registration() {
+    it("should save u2f meta and return status code 200", function () {
+      const expectedStatus = {
+        keyHandle: "keyHandle",
+        publicKey: "pbk",
+        certificate: "cert"
+      };
+      mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve(expectedStatus));
+
+      authSession.register_request = {
+        appId: "app",
+        challenge: "challenge",
+        keyHandle: "key",
+        version: "U2F_V2"
+      };
+      return U2FRegisterPost.default(vars)(req as any, res as any)
+        .then(function () {
+          assert.equal("user", mocks.userDataStore.saveU2FRegistrationStub.getCall(0).args[0]);
+          assert.equal(authSession.identity_check, undefined);
+        });
+    });
+
+    it("should return error message on finishRegistration error", function () {
+      mocks.u2f.checkRegistrationStub.returns({ errorCode: 500 });
+
+      authSession.register_request = {
+        appId: "app",
+        challenge: "challenge",
+        keyHandle: "key",
+        version: "U2F_V2"
+      };
+
+      return U2FRegisterPost.default(vars)(req as any, res as any)
+        .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
+        .catch(function () {
+          assert.equal(200, res.status.getCall(0).args[0]);
+          assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "Operation failed."
+          });
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should return error message when register_request is not provided", function () {
+      mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve());
+      authSession.register_request = undefined;
+      return U2FRegisterPost.default(vars)(req as any, res as any)
+        .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
+        .catch(function () {
+          assert.equal(200, res.status.getCall(0).args[0]);
+          assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "Operation failed."
+          });
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should return error message when no auth request has been initiated", function () {
+      mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve());
+      authSession.register_request = undefined;
+      return U2FRegisterPost.default(vars)(req as any, res as any)
+        .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
+        .catch(function () {
+          assert.equal(200, res.status.getCall(0).args[0]);
+          assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "Operation failed."
+          });
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should return error message when identity has not been verified", function () {
+      authSession.identity_check = undefined;
+      return U2FRegisterPost.default(vars)(req as any, res as any)
+        .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
+        .catch(function () {
+          assert.equal(200, res.status.getCall(0).args[0]);
+          assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "Operation failed."
+          });
+          return BluebirdPromise.resolve();
+        });
+    });
+  }
+});
+
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/u2f/register/post.ts b/themes/triangles/server/src/lib/routes/secondfactor/u2f/register/post.ts
new file mode 100644
index 00000000..7296ccbe
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/u2f/register/post.ts
@@ -0,0 +1,64 @@
+
+import { UserDataStore } from "../../../../storage/UserDataStore";
+import objectPath = require("object-path");
+import u2f_common = require("../U2FCommon");
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import U2f = require("u2f");
+import { U2FRegistration } from "../../../../../../types/U2FRegistration";
+import redirect from "../../redirect";
+import ErrorReplies = require("../../../../ErrorReplies");
+import { ServerVariables } from "../../../../ServerVariables";
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+
+
+export default function (vars: ServerVariables) {
+  function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    const appid = u2f_common.extract_app_id(req);
+    const registrationResponse: U2f.RegistrationData = req.body;
+
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      const registrationRequest = authSession.register_request;
+
+      if (!registrationRequest) {
+        return reject(new Error("No registration request"));
+      }
+
+      if (!authSession.identity_check
+        || authSession.identity_check.challenge != "u2f-register") {
+        return reject(new Error("Bad challenge for registration request"));
+      }
+
+      vars.logger.info(req, "Finishing registration");
+      vars.logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
+      vars.logger.debug(req, "RegistrationResponse = %s", JSON.stringify(registrationResponse));
+
+      return resolve(vars.u2f.checkRegistration(registrationRequest, registrationResponse));
+    })
+      .then(function (u2fResult: U2f.RegistrationResult | U2f.Error): BluebirdPromise<void> {
+        if (objectPath.has(u2fResult, "errorCode"))
+          return BluebirdPromise.reject(new Error("Error while registering."));
+
+        const registrationResult: U2f.RegistrationResult = u2fResult as U2f.RegistrationResult;
+        vars.logger.info(req, "Store registration and reply");
+        vars.logger.debug(req, "RegistrationResult = %s", JSON.stringify(registrationResult));
+        const registration: U2FRegistration = {
+          keyHandle: registrationResult.keyHandle,
+          publicKey: registrationResult.publicKey
+        };
+        return vars.userDataStore.saveU2FRegistration(authSession.userid, appid, registration);
+      })
+      .then(function () {
+        authSession.identity_check = undefined;
+        redirect(vars)(req, res);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  }
+  return handler;
+}
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/u2f/register_request/get.spec.ts b/themes/triangles/server/src/lib/routes/secondfactor/u2f/register_request/get.spec.ts
new file mode 100644
index 00000000..a207c910
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/u2f/register_request/get.spec.ts
@@ -0,0 +1,86 @@
+
+import sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+import U2FRegisterRequestGet = require("./get");
+import ExpressMock = require("../../../../stubs/express.spec");
+import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec";
+import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../../ServerVariables";
+
+describe("routes/secondfactor/u2f/register_request/get", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    req = ExpressMock.RequestMock();
+    req.originalUrl = "/api/xxxx";
+    req.app = {};
+    req.session = {
+      auth: {
+        userid: "user",
+        first_factor: true,
+        second_factor: false,
+        identity_check: {
+          challenge: "u2f-register",
+          userid: "user"
+        }
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
+
+    res = ExpressMock.ResponseMock();
+    res.send = sinon.spy();
+    res.json = sinon.spy();
+    res.status = sinon.spy();
+  });
+
+  describe("test registration request", () => {
+    it("should send back the registration request and save it in the session", function () {
+      const expectedRequest = {
+        test: "abc"
+      };
+      mocks.u2f.requestStub.returns(BluebirdPromise.resolve(expectedRequest));
+      return U2FRegisterRequestGet.default(vars)(req as any, res as any)
+        .then(function () {
+          Assert.deepEqual(expectedRequest, res.json.getCall(0).args[0]);
+        });
+    });
+
+    it("should return internal error on registration request", function () {
+      res.send = sinon.spy();
+      const user_key_container = {};
+      mocks.u2f.requestStub.returns(BluebirdPromise.reject("Internal error"));
+      return U2FRegisterRequestGet.default(vars)(req as any, res as any)
+        .then(function () {
+          Assert.equal(res.status.getCall(0).args[0], 200);
+          Assert.deepEqual(res.send.getCall(0).args[0], {
+            error: "Operation failed."
+          });
+        });
+    });
+
+    it("should return forbidden if identity has not been verified", function () {
+      req.session.auth.identity_check = undefined;
+      return U2FRegisterRequestGet.default(vars)(req as any, res as any)
+        .then(function () {
+          Assert.equal(403, res.status.getCall(0).args[0]);
+        });
+    });
+  });
+});
+
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/u2f/register_request/get.ts b/themes/triangles/server/src/lib/routes/secondfactor/u2f/register_request/get.ts
new file mode 100644
index 00000000..f611af93
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/u2f/register_request/get.ts
@@ -0,0 +1,43 @@
+
+import { UserDataStore } from "../../../../storage/UserDataStore";
+
+import objectPath = require("object-path");
+import u2f_common = require("../U2FCommon");
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import U2f = require("u2f");
+import ErrorReplies = require("../../../../ErrorReplies");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import { ServerVariables } from "../../../../ServerVariables";
+
+export default function (vars: ServerVariables) {
+  function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    const appid: string = u2f_common.extract_app_id(req);
+
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      if (!authSession.identity_check
+        || authSession.identity_check.challenge != "u2f-register") {
+        res.status(403);
+        res.send();
+        return reject(new Error("Bad challenge."));
+      }
+
+      vars.logger.info(req, "Starting registration for appId '%s'", appid);
+      return resolve(vars.u2f.request(appid));
+    })
+      .then(function (registrationRequest: U2f.Request) {
+        vars.logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
+        authSession.register_request = registrationRequest;
+        res.json(registrationRequest);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  }
+
+  return handler;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign/post.spec.ts b/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign/post.spec.ts
new file mode 100644
index 00000000..9b137e66
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign/post.spec.ts
@@ -0,0 +1,101 @@
+
+import sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import Assert = require("assert");
+import U2FSignPost = require("./post");
+import { ServerVariables } from "../../../../ServerVariables";
+import winston = require("winston");
+
+import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../ServerVariablesMockBuilder.spec";
+import ExpressMock = require("../../../../stubs/express.spec");
+import U2FMock = require("../../../../stubs/u2f.spec");
+import U2f = require("u2f");
+import { Level } from "../../../../authentication/Level";
+
+describe("routes/secondfactor/u2f/sign/post", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    req = ExpressMock.RequestMock();
+    req.app = {};
+    req.originalUrl = "/api/xxxx";
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    req.session = {
+      auth: {
+        userid: "user",
+        authentication_level: Level.ONE_FACTOR,
+        identity_check: {
+          challenge: "u2f-register",
+          userid: "user"
+        }
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const options = {
+      inMemoryOnly: true
+    };
+
+    res = ExpressMock.ResponseMock();
+    res.send = sinon.spy();
+    res.json = sinon.spy();
+    res.status = sinon.spy();
+  });
+
+  it("should return status code 204", function () {
+    const expectedStatus = {
+      keyHandle: "keyHandle",
+      publicKey: "pbk",
+      certificate: "cert"
+    };
+    mocks.u2f.checkSignatureStub.returns(expectedStatus);
+
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({
+      registration: {
+        publicKey: "PUBKEY"
+      }
+    }));
+
+    req.session.auth.sign_request = {
+      appId: "app",
+      challenge: "challenge",
+      keyHandle: "key",
+      version: "U2F_V2"
+    };
+    return U2FSignPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal(req.session.auth.authentication_level, Level.TWO_FACTOR);
+      });
+  });
+
+  it("should return unauthorized error on registration request internal error", function () {
+    mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({
+      registration: {
+        publicKey: "PUBKEY"
+      }
+    }));
+    mocks.u2f.checkSignatureStub.returns({ errorCode: 500 });
+
+    req.session.auth.sign_request = {
+      appId: "app",
+      challenge: "challenge",
+      keyHandle: "key",
+      version: "U2F_V2"
+    };
+    return U2FSignPost.default(vars)(req as any, res as any)
+      .then(function () {
+        Assert.equal(res.status.getCall(0).args[0], 200);
+        Assert.deepEqual(res.send.getCall(0).args[0],
+          { error: "Operation failed." });
+      });
+  });
+});
+
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign/post.ts b/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign/post.ts
new file mode 100644
index 00000000..7ee711c2
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign/post.ts
@@ -0,0 +1,57 @@
+
+import objectPath = require("object-path");
+import u2f_common = require("../U2FCommon");
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import { UserDataStore } from "../../../../storage/UserDataStore";
+import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument";
+import { Winston } from "../../../../../../types/Dependencies";
+import U2f = require("u2f");
+import exceptions = require("../../../../Exceptions");
+import redirect from "../../redirect";
+import ErrorReplies = require("../../../../ErrorReplies");
+import { ServerVariables } from "../../../../ServerVariables";
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+import { Level } from "../../../../authentication/Level";
+
+export default function (vars: ServerVariables) {
+  function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    const appId = u2f_common.extract_app_id(req);
+
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      if (!authSession.sign_request) {
+        const err = new Error("No sign request");
+        ErrorReplies.replyWithError401(req, res, vars.logger)(err);
+        return reject(err);
+      }
+      resolve();
+    })
+      .then(function () {
+        const userid = authSession.userid;
+        return vars.userDataStore.retrieveU2FRegistration(userid, appId);
+      })
+      .then(function (doc: U2FRegistrationDocument): BluebirdPromise<U2f.SignatureResult | U2f.Error> {
+        const signRequest = authSession.sign_request;
+        const signData: U2f.SignatureData = req.body;
+        vars.logger.info(req, "Finish authentication");
+        return BluebirdPromise.resolve(vars.u2f.checkSignature(signRequest, signData, doc.registration.publicKey));
+      })
+      .then(function (result: U2f.SignatureResult | U2f.Error): BluebirdPromise<void> {
+        if (objectPath.has(result, "errorCode"))
+          return BluebirdPromise.reject(new Error("Error while signing"));
+        vars.logger.info(req, "Successful authentication");
+        authSession.authentication_level = Level.TWO_FACTOR;
+        redirect(vars)(req, res);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  }
+
+  return handler;
+}
+
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts b/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts
new file mode 100644
index 00000000..dd52b27e
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts
@@ -0,0 +1,68 @@
+
+import sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+import assert = require("assert");
+import U2FSignRequestGet = require("./get");
+import ExpressMock = require("../../../../stubs/express.spec");
+import { Request } from "u2f";
+import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec";
+import { ServerVariables } from "../../../../ServerVariables";
+
+import { SignMessage } from "../../../../../../../shared/SignMessage";
+
+describe("routes/secondfactor/u2f/sign_request/get", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+
+  beforeEach(function () {
+    req = ExpressMock.RequestMock();
+    req.originalUrl = "/api/xxxx";
+    req.app = {};
+    req.session = {
+      auth: {
+        userid: "user",
+        first_factor: true,
+        second_factor: false,
+        identity_check: {
+          challenge: "u2f-register",
+          userid: "user"
+        }
+      }
+    };
+    req.headers = {};
+    req.headers.host = "localhost";
+
+    const s = ServerVariablesMockBuilder.build();
+    mocks = s.mocks;
+    vars = s.variables;
+
+    res = ExpressMock.ResponseMock();
+    res.send = sinon.spy();
+    res.json = sinon.spy();
+    res.status = sinon.spy();
+  });
+
+  it("should send back the sign request and save it in the session", function () {
+    const expectedRequest: Request = {
+      version: "U2F_V2",
+      appId: 'app',
+      challenge: 'challenge!'
+    };
+    mocks.u2f.requestStub.returns(expectedRequest);
+    mocks.userDataStore.retrieveU2FRegistrationStub
+      .returns(BluebirdPromise.resolve({
+        registration: {
+          keyHandle: "KeyHandle"
+        }
+      }));
+
+    return U2FSignRequestGet.default(vars)(req as any, res as any)
+      .then(() => {
+        assert.deepEqual(expectedRequest, req.session.auth.sign_request);
+        assert.deepEqual(expectedRequest, res.json.getCall(0).args[0]);
+      });
+  });
+});
+
diff --git a/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts b/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts
new file mode 100644
index 00000000..9e93dde0
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts
@@ -0,0 +1,42 @@
+
+import u2f_common = require("../../../secondfactor/u2f/U2FCommon");
+import BluebirdPromise = require("bluebird");
+import express = require("express");
+import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument";
+import exceptions = require("../../../../Exceptions");
+import ErrorReplies = require("../../../../ErrorReplies");
+import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
+import UserMessages = require("../../../../../../../shared/UserMessages");
+import { ServerVariables } from "../../../../ServerVariables";
+import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
+
+export default function (vars: ServerVariables) {
+  function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    const appId = u2f_common.extract_app_id(req);
+
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      resolve();
+    })
+      .then(function () {
+        return vars.userDataStore.retrieveU2FRegistration(authSession.userid, appId);
+      })
+      .then(function (doc: U2FRegistrationDocument): BluebirdPromise<void> {
+        if (!doc)
+          return BluebirdPromise.reject(new exceptions.AccessDeniedError("No U2F registration document found."));
+
+        const appId: string = u2f_common.extract_app_id(req);
+        vars.logger.info(req, "Start authentication of app '%s'", appId);
+        vars.logger.debug(req, "AppId = %s, keyHandle = %s", appId, JSON.stringify(doc.registration.keyHandle));
+
+        const request = vars.u2f.request(appId, doc.registration.keyHandle);
+        authSession.sign_request = request;
+        res.json(request);
+        return BluebirdPromise.resolve();
+      })
+      .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
+        UserMessages.OPERATION_FAILED));
+  }
+  return handler;
+}
diff --git a/themes/triangles/server/src/lib/routes/verify/access_control.ts b/themes/triangles/server/src/lib/routes/verify/access_control.ts
new file mode 100644
index 00000000..136239ae
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/verify/access_control.ts
@@ -0,0 +1,51 @@
+import Express = require("express");
+import BluebirdPromise = require("bluebird");
+import Util = require("util");
+
+import Exceptions = require("../../Exceptions");
+
+import { Level as AuthorizationLevel } from "../../authorization/Level";
+import { Level as AuthenticationLevel } from "../../authentication/Level";
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import { ServerVariables } from "../../ServerVariables";
+
+function isAuthorized(
+  authorization: AuthorizationLevel,
+  authentication: AuthenticationLevel): boolean {
+
+  if (authorization == AuthorizationLevel.BYPASS) {
+    return true;
+  } else if (authorization == AuthorizationLevel.ONE_FACTOR &&
+    authentication >= AuthenticationLevel.ONE_FACTOR) {
+    return true;
+  } else if (authorization == AuthorizationLevel.TWO_FACTOR &&
+    authentication >= AuthenticationLevel.TWO_FACTOR) {
+    return true;
+  }
+  return false;
+}
+
+export default function (
+  req: Express.Request,
+  vars: ServerVariables,
+  domain: string, resource: string,
+  user: string, groups: string[],
+  authenticationLevel: AuthenticationLevel) {
+
+  return new BluebirdPromise(function (resolve, reject) {
+    const authorizationLevel = vars.authorizer
+      .authorization({domain, resource}, {user, groups});
+
+    if (!isAuthorized(authorizationLevel, authenticationLevel)) {
+      if (authorizationLevel == AuthorizationLevel.DENY) {
+        reject(new Exceptions.NotAuthorizedError(
+          Util.format("User %s is not authorized to access %s%s", user, domain, resource)));
+        return;
+      }
+      reject(new Exceptions.NotAuthenticatedError(Util.format(
+        "User '%s' is not sufficiently authorized to access %s%s.", user, domain, resource)));
+      return;
+    }
+    resolve();
+  });
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/verify/get.spec.ts b/themes/triangles/server/src/lib/routes/verify/get.spec.ts
new file mode 100644
index 00000000..67cf19fb
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/verify/get.spec.ts
@@ -0,0 +1,320 @@
+
+import Assert = require("assert");
+import BluebirdPromise = require("bluebird");
+import Express = require("express");
+import Sinon = require("sinon");
+import winston = require("winston");
+
+import VerifyGet = require("./get");
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import { AuthenticationSession } from "../../../../types/AuthenticationSession";
+import ExpressMock = require("../../stubs/express.spec");
+import { ServerVariables } from "../../ServerVariables";
+import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../ServerVariablesMockBuilder.spec";
+import { Level } from "../../authentication/Level";
+import { Level as AuthorizationLevel } from "../../authorization/Level";
+
+describe("routes/verify/get", function () {
+  let req: ExpressMock.RequestMock;
+  let res: ExpressMock.ResponseMock;
+  let mocks: ServerVariablesMock;
+  let vars: ServerVariables;
+  let authSession: AuthenticationSession;
+
+  beforeEach(function () {
+    req = ExpressMock.RequestMock();
+    res = ExpressMock.ResponseMock();
+    req.originalUrl = "/api/xxxx";
+    req.query = {
+      redirect: "undefined"
+    };
+    AuthenticationSessionHandler.reset(req as any);
+    req.headers["x-original-url"] = "https://secret.example.com/";
+    const s = ServerVariablesMockBuilder.build(false);
+    mocks = s.mocks;
+    vars = s.variables;
+    authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
+  });
+
+  describe("with session cookie", function () {
+    it("should be already authenticated", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      authSession.authentication_level = Level.TWO_FACTOR;
+      authSession.userid = "myuser";
+      authSession.groups = ["mygroup", "othergroup"];
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "myuser");
+          Sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup");
+          Assert.equal(204, res.status.getCall(0).args[0]);
+        });
+    });
+
+    function test_session(_authSession: AuthenticationSession, status_code: number) {
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert.equal(status_code, res.status.getCall(0).args[0]);
+        });
+    }
+
+    function test_non_authenticated_401(authSession: AuthenticationSession) {
+      return test_session(authSession, 401);
+    }
+
+    function test_unauthorized_403(authSession: AuthenticationSession) {
+      return test_session(authSession, 403);
+    }
+
+    function test_authorized(authSession: AuthenticationSession) {
+      return test_session(authSession, 204);
+    }
+
+    describe("given user tries to access a 2-factor endpoint", function () {
+      before(function () {
+        mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      });
+
+      describe("given different cases of session", function () {
+        it("should not be authenticated when second factor is missing", function () {
+          return test_non_authenticated_401({
+            keep_me_logged_in: false,
+            userid: "user",
+            authentication_level: Level.ONE_FACTOR,
+            email: undefined,
+            groups: [],
+            last_activity_datetime: new Date().getTime()
+          });
+        });
+
+        it("should not be authenticated when userid is missing", function () {
+          return test_non_authenticated_401({
+            keep_me_logged_in: false,
+            userid: undefined,
+            authentication_level: Level.TWO_FACTOR,
+            email: undefined,
+            groups: [],
+            last_activity_datetime: new Date().getTime()
+          });
+        });
+
+        it("should not be authenticated when level is insufficient", function () {
+          return test_non_authenticated_401({
+            keep_me_logged_in: false,
+            userid: "user",
+            authentication_level: Level.NOT_AUTHENTICATED,
+            email: undefined,
+            groups: [],
+            last_activity_datetime: new Date().getTime()
+          });
+        });
+
+        it("should not be authenticated when session has not be initiated", function () {
+          return test_non_authenticated_401(undefined);
+        });
+
+        it("should not be authenticated when domain is not allowed for user", function () {
+          authSession.authentication_level = Level.TWO_FACTOR;
+          authSession.userid = "myuser";
+          req.headers["x-original-url"] = "https://test.example.com/";
+          mocks.authorizer.authorizationMock.returns(AuthorizationLevel.DENY);
+
+          return test_unauthorized_403({
+            keep_me_logged_in: false,
+            authentication_level: Level.TWO_FACTOR,
+            userid: "user",
+            groups: ["group1", "group2"],
+            email: undefined,
+            last_activity_datetime: new Date().getTime()
+          });
+        });
+      });
+    });
+
+    describe("given user tries to access a single factor endpoint", function () {
+      beforeEach(function () {
+        req.headers["x-original-url"] = "https://redirect.url/";
+      });
+
+      it("should be authenticated when first factor is validated", function () {
+        mocks.authorizer.authorizationMock.returns(AuthorizationLevel.ONE_FACTOR);
+        authSession.authentication_level = Level.ONE_FACTOR;
+        authSession.userid = "user1";
+        return VerifyGet.default(vars)(req as Express.Request, res as any)
+          .then(function () {
+            Assert(res.status.calledWith(204));
+            Assert(res.send.calledOnce);
+          });
+      });
+
+      it("should be rejected with 401 when not authenticated", function () {
+        mocks.authorizer.authorizationMock.returns(AuthorizationLevel.ONE_FACTOR);
+        authSession.authentication_level = Level.NOT_AUTHENTICATED;
+        return VerifyGet.default(vars)(req as Express.Request, res as any)
+          .then(function () {
+            Assert(res.status.calledWith(401));
+          });
+      });
+    });
+
+    describe("inactivity period", function () {
+      it("should update last inactivity period on requests on /api/verify", function () {
+        mocks.config.session.inactivity = 200000;
+        mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+        const currentTime = new Date().getTime() - 1000;
+        AuthenticationSessionHandler.reset(req as any);
+        authSession.authentication_level = Level.TWO_FACTOR;
+        authSession.userid = "myuser";
+        authSession.groups = ["mygroup", "othergroup"];
+        authSession.last_activity_datetime = currentTime;
+        return VerifyGet.default(vars)(req as Express.Request, res as any)
+          .then(function () {
+            return AuthenticationSessionHandler.get(req as any, vars.logger);
+          })
+          .then(function (authSession) {
+            Assert(authSession.last_activity_datetime > currentTime);
+          });
+      });
+
+      it("should reset session when max inactivity period has been reached", function () {
+        mocks.config.session.inactivity = 1;
+        mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+        const currentTime = new Date().getTime() - 1000;
+        AuthenticationSessionHandler.reset(req as any);
+        authSession.authentication_level = Level.TWO_FACTOR;
+        authSession.userid = "myuser";
+        authSession.groups = ["mygroup", "othergroup"];
+        authSession.last_activity_datetime = currentTime;
+        return VerifyGet.default(vars)(req as Express.Request, res as any)
+          .then(function () {
+            return AuthenticationSessionHandler.get(req as any, vars.logger);
+          })
+          .then(function (authSession) {
+            Assert.equal(authSession.authentication_level, Level.NOT_AUTHENTICATED);
+            Assert.equal(authSession.userid, undefined);
+          });
+      });
+    });
+  });
+
+  describe("response type 401 | 302", function() {
+    it("should return error code 401", function() {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.rejects(new Error(
+        "Invalid credentials"));
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+
+    it("should redirect to provided redirection url", function() {
+      const REDIRECT_URL = "http://redirection_url.com";
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.rejects(new Error(
+        "Invalid credentials"));
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+      req.query["rd"] = REDIRECT_URL;
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.redirect.calledWithExactly(REDIRECT_URL));
+        });
+    });
+  });
+
+  describe("with basic auth", function () {
+    it("should authenticate correctly", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.ONE_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.returns({
+        groups: ["mygroup", "othergroup"],
+      });
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "john");
+          Sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup");
+          Assert.equal(204, res.status.getCall(0).args[0]);
+        });
+    });
+
+    it("should fail when endpoint is protected by two factors", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.config.access_control.rules = [{
+        domain: "secret.example.com",
+        policy: "two_factor"
+      }];
+      mocks.usersDatabase.checkUserPasswordStub.resolves({
+        groups: ["mygroup", "othergroup"],
+      });
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+
+    it("should fail when base64 token is not valid", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.resolves({
+        groups: ["mygroup", "othergroup"],
+      });
+      req.headers["proxy-authorization"] = "Basic i_m*not_a_base64*token";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+
+    it("should fail when base64 token has not format user:psswd", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.resolves({
+        groups: ["mygroup", "othergroup"],
+      });
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzOmJhZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+
+    it("should fail when bad user password is provided", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.rejects(new Error(
+        "Invalid credentials"));
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+
+    it("should fail when resource is restricted", function () {
+      mocks.authorizer.authorizationMock.returns(AuthorizationLevel.TWO_FACTOR);
+      mocks.config.access_control.default_policy = "one_factor";
+      mocks.usersDatabase.checkUserPasswordStub.resolves({
+        groups: ["mygroup", "othergroup"],
+      });
+      req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA==";
+
+      return VerifyGet.default(vars)(req as Express.Request, res as any)
+        .then(function () {
+          Assert(res.status.calledWithExactly(401));
+        });
+    });
+  });
+});
+
diff --git a/themes/triangles/server/src/lib/routes/verify/get.ts b/themes/triangles/server/src/lib/routes/verify/get.ts
new file mode 100644
index 00000000..f7386169
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/verify/get.ts
@@ -0,0 +1,91 @@
+import BluebirdPromise = require("bluebird");
+import Express = require("express");
+import Exceptions = require("../../Exceptions");
+import ErrorReplies = require("../../ErrorReplies");
+import { ServerVariables } from "../../ServerVariables";
+import GetWithSessionCookieMethod from "./get_session_cookie";
+import GetWithBasicAuthMethod from "./get_basic_auth";
+import Constants = require("../../../../../shared/constants");
+import ObjectPath = require("object-path");
+
+import { AuthenticationSessionHandler }
+  from "../../AuthenticationSessionHandler";
+import { AuthenticationSession }
+  from "../../../../types/AuthenticationSession";
+
+const REMOTE_USER = "Remote-User";
+const REMOTE_GROUPS = "Remote-Groups";
+
+
+function verifyWithSelectedMethod(req: Express.Request, res: Express.Response,
+  vars: ServerVariables, authSession: AuthenticationSession)
+  : () => BluebirdPromise<{ username: string, groups: string[] }> {
+  return function () {
+    const authorization: string = "" + req.headers["proxy-authorization"];
+    if (authorization && authorization.startsWith("Basic "))
+      return GetWithBasicAuthMethod(req, res, vars, authorization);
+
+    return GetWithSessionCookieMethod(req, res, vars, authSession);
+  };
+}
+
+function setRedirectHeader(req: Express.Request, res: Express.Response) {
+  return function () {
+    const originalUrl = ObjectPath.get<Express.Request, string>(
+      req, "headers.x-original-url");
+    res.set("Redirect", originalUrl);
+    return BluebirdPromise.resolve();
+  };
+}
+
+function setUserAndGroupsHeaders(res: Express.Response) {
+  return function (u: { username: string, groups: string[] }) {
+    res.setHeader(REMOTE_USER, u.username);
+    res.setHeader(REMOTE_GROUPS, u.groups.join(","));
+    return BluebirdPromise.resolve();
+  };
+}
+
+function replyWith200(res: Express.Response) {
+  return function () {
+    res.status(204);
+    res.send();
+  };
+}
+
+function getRedirectParam(req: Express.Request) {
+  return req.query[Constants.REDIRECT_QUERY_PARAM] != "undefined"
+    ? req.query[Constants.REDIRECT_QUERY_PARAM]
+    : undefined;
+}
+
+export default function (vars: ServerVariables) {
+  return function (req: Express.Request, res: Express.Response)
+    : BluebirdPromise<void> {
+    let authSession: AuthenticationSession;
+    return new BluebirdPromise(function (resolve, reject) {
+      authSession = AuthenticationSessionHandler.get(req, vars.logger);
+      resolve();
+    })
+      .then(setRedirectHeader(req, res))
+      .then(verifyWithSelectedMethod(req, res, vars, authSession))
+      .then(setUserAndGroupsHeaders(res))
+      .then(replyWith200(res))
+      // The user is authenticated but has restricted access -> 403
+      .catch(Exceptions.NotAuthorizedError,
+        ErrorReplies.replyWithError403(req, res, vars.logger))
+      .catch(Exceptions.NotAuthenticatedError,
+        ErrorReplies.replyWithError401(req, res, vars.logger))
+      // The user is not yet authenticated -> 401
+      .catch((err) => {
+        const redirectUrl = getRedirectParam(req);
+        if (redirectUrl) {
+          ErrorReplies.redirectTo(redirectUrl, req, res, vars.logger)(err);
+        }
+        else {
+          ErrorReplies.replyWithError401(req, res, vars.logger)(err);
+        }
+      });
+  };
+}
+
diff --git a/themes/triangles/server/src/lib/routes/verify/get_basic_auth.ts b/themes/triangles/server/src/lib/routes/verify/get_basic_auth.ts
new file mode 100644
index 00000000..af23c76c
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/verify/get_basic_auth.ts
@@ -0,0 +1,55 @@
+import Express = require("express");
+import BluebirdPromise = require("bluebird");
+import ObjectPath = require("object-path");
+import { ServerVariables } from "../../ServerVariables";
+import { AuthenticationSession }
+  from "../../../../types/AuthenticationSession";
+import AccessControl from "./access_control";
+import { URLDecomposer } from "../../utils/URLDecomposer";
+import { Level } from "../../authentication/Level";
+
+export default function (req: Express.Request, res: Express.Response,
+  vars: ServerVariables, authorizationHeader: string)
+  : BluebirdPromise<{ username: string, groups: string[] }> {
+  let username: string;
+  const uri = ObjectPath.get<Express.Request, string>(req, "headers.x-original-url");
+  const urlDecomposition = URLDecomposer.fromUrl(uri);
+
+  return BluebirdPromise.resolve()
+    .then(() => {
+      const base64Re = new RegExp("^Basic ((?:[A-Za-z0-9+/]{4})*" +
+        "(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)$");
+      const isTokenValidBase64 = base64Re.test(authorizationHeader);
+
+      if (!isTokenValidBase64) {
+        return BluebirdPromise.reject(new Error("No valid base64 token found in the header"));
+      }
+
+      const tokenMatches = authorizationHeader.match(base64Re);
+      const base64Token = tokenMatches[1];
+      const decodedToken = Buffer.from(base64Token, "base64").toString();
+      const splittedToken = decodedToken.split(":");
+
+      if (splittedToken.length != 2) {
+        return BluebirdPromise.reject(new Error(
+          "The authorization token is invalid. Expecting 'userid:password'"));
+      }
+
+      username = splittedToken[0];
+      const password = splittedToken[1];
+      return vars.usersDatabase.checkUserPassword(username, password);
+    })
+    .then(function (groupsAndEmails) {
+      return AccessControl(req, vars, urlDecomposition.domain, urlDecomposition.path,
+        username, groupsAndEmails.groups, Level.ONE_FACTOR)
+        .then(() => BluebirdPromise.resolve({
+          username: username,
+          groups: groupsAndEmails.groups
+        }));
+    })
+    .catch(function (err: Error) {
+      return BluebirdPromise.reject(
+        new Error("Unable to authenticate the user with basic auth. Cause: "
+          + err.message));
+    });
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/routes/verify/get_session_cookie.ts b/themes/triangles/server/src/lib/routes/verify/get_session_cookie.ts
new file mode 100644
index 00000000..07034481
--- /dev/null
+++ b/themes/triangles/server/src/lib/routes/verify/get_session_cookie.ts
@@ -0,0 +1,78 @@
+import Express = require("express");
+import BluebirdPromise = require("bluebird");
+import Util = require("util");
+import ObjectPath = require("object-path");
+
+import Exceptions = require("../../Exceptions");
+import { Configuration } from "../../configuration/schema/Configuration";
+import { ServerVariables } from "../../ServerVariables";
+import { IRequestLogger } from "../../logging/IRequestLogger";
+import { AuthenticationSession }
+  from "../../../../types/AuthenticationSession";
+import { AuthenticationSessionHandler }
+  from "../../AuthenticationSessionHandler";
+import AccessControl from "./access_control";
+import { URLDecomposer } from "../../utils/URLDecomposer";
+
+function verify_inactivity(req: Express.Request,
+  authSession: AuthenticationSession,
+  configuration: Configuration, logger: IRequestLogger)
+  : BluebirdPromise<void> {
+
+  // If inactivity is not specified, then inactivity timeout does not apply
+  if (!configuration.session.inactivity || authSession.keep_me_logged_in) {
+    return BluebirdPromise.resolve();
+  }
+
+  const lastActivityTime = authSession.last_activity_datetime;
+  const currentTime = new Date().getTime();
+  authSession.last_activity_datetime = currentTime;
+
+  const inactivityPeriodMs = currentTime - lastActivityTime;
+  logger.debug(req, "Inactivity period was %s s and max period was %s.",
+    inactivityPeriodMs / 1000, configuration.session.inactivity / 1000);
+  if (inactivityPeriodMs < configuration.session.inactivity) {
+    return BluebirdPromise.resolve();
+  }
+
+  logger.debug(req, "Session has been reset after too long inactivity period.");
+  AuthenticationSessionHandler.reset(req);
+  return BluebirdPromise.reject(new Error("Inactivity period exceeded."));
+}
+
+export default function (req: Express.Request, res: Express.Response,
+  vars: ServerVariables, authSession: AuthenticationSession)
+  : BluebirdPromise<{ username: string, groups: string[] }> {
+
+  return BluebirdPromise.resolve()
+    .then(() => {
+    const username = authSession.userid;
+    const groups = authSession.groups;
+
+    if (!authSession.userid) {
+      return BluebirdPromise.reject(new Exceptions.AccessDeniedError(
+        "userid is missing"));
+    }
+
+    const originalUrl = ObjectPath.get<Express.Request, string>(
+      req, "headers.x-original-url");
+    const originalUri =
+      ObjectPath.get<Express.Request, string>(req, "headers.x-original-uri");
+
+    const d = URLDecomposer.fromUrl(originalUrl);
+    vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", d.domain,
+      d.path, username, groups.join(","));
+    return AccessControl(req, vars, d.domain, d.path, username, groups,
+      authSession.authentication_level);
+  })
+    .then(() => {
+      return verify_inactivity(req, authSession,
+        vars.config, vars.logger);
+    })
+    .then(() => {
+      return BluebirdPromise.resolve({
+        username: authSession.userid,
+        groups: authSession.groups
+      });
+    });
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/AuthenticationTraceDocument.d.ts b/themes/triangles/server/src/lib/storage/AuthenticationTraceDocument.d.ts
new file mode 100644
index 00000000..69818c05
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/AuthenticationTraceDocument.d.ts
@@ -0,0 +1,6 @@
+
+export interface AuthenticationTraceDocument {
+    userId: string;
+    date: Date;
+    isAuthenticationSuccessful: boolean;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/CollectionFactoryFactory.ts b/themes/triangles/server/src/lib/storage/CollectionFactoryFactory.ts
new file mode 100644
index 00000000..92b29abf
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/CollectionFactoryFactory.ts
@@ -0,0 +1,15 @@
+import { ICollectionFactory } from "./ICollectionFactory";
+import { NedbCollectionFactory } from "./nedb/NedbCollectionFactory";
+import { MongoCollectionFactory } from "./mongo/MongoCollectionFactory";
+import { IMongoClient } from "../connectors/mongo/IMongoClient";
+
+
+export class CollectionFactoryFactory {
+  static createNedb(options: Nedb.DataStoreOptions): ICollectionFactory {
+    return new NedbCollectionFactory(options);
+  }
+
+  static createMongo(client: IMongoClient): ICollectionFactory {
+    return new MongoCollectionFactory(client);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/CollectionFactoryStub.spec.ts b/themes/triangles/server/src/lib/storage/CollectionFactoryStub.spec.ts
new file mode 100644
index 00000000..17f8bb02
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/CollectionFactoryStub.spec.ts
@@ -0,0 +1,16 @@
+import BluebirdPromise = require("bluebird");
+import Sinon = require("sinon");
+import { ICollection } from "./ICollection";
+import { ICollectionFactory } from "./ICollectionFactory";
+
+export class CollectionFactoryStub implements ICollectionFactory {
+    buildStub: Sinon.SinonStub;
+
+    constructor() {
+        this.buildStub = Sinon.stub();
+    }
+
+    build(collectionName: string): ICollection {
+        return this.buildStub(collectionName);
+    }
+}
diff --git a/themes/triangles/server/src/lib/storage/CollectionStub.spec.ts b/themes/triangles/server/src/lib/storage/CollectionStub.spec.ts
new file mode 100644
index 00000000..42895d67
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/CollectionStub.spec.ts
@@ -0,0 +1,39 @@
+import BluebirdPromise = require("bluebird");
+import Sinon = require("sinon");
+import { ICollection } from "./ICollection";
+
+export class CollectionStub implements ICollection {
+    findStub: Sinon.SinonStub;
+    findOneStub: Sinon.SinonStub;
+    updateStub: Sinon.SinonStub;
+    removeStub: Sinon.SinonStub;
+    insertStub: Sinon.SinonStub;
+
+    constructor() {
+        this.findStub = Sinon.stub();
+        this.findOneStub = Sinon.stub();
+        this.updateStub = Sinon.stub();
+        this.removeStub = Sinon.stub();
+        this.insertStub = Sinon.stub();
+    }
+
+    find(filter: any, sortKeys: any, count: number): BluebirdPromise<any> {
+        return this.findStub(filter, sortKeys, count);
+    }
+
+    findOne(filter: any): BluebirdPromise<any> {
+        return this.findOneStub(filter);
+    }
+
+    update(filter: any, document: any, options: any): BluebirdPromise<any> {
+        return this.updateStub(filter, document, options);
+    }
+
+    remove(filter: any): BluebirdPromise<any> {
+        return this.removeStub(filter);
+    }
+
+    insert(document: any): BluebirdPromise<any> {
+        return this.insertStub(document);
+    }
+}
diff --git a/themes/triangles/server/src/lib/storage/ICollection.d.ts b/themes/triangles/server/src/lib/storage/ICollection.d.ts
new file mode 100644
index 00000000..caa6c2a8
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/ICollection.d.ts
@@ -0,0 +1,11 @@
+/* istanbul ignore next */
+import BluebirdPromise = require("bluebird");
+
+/* istanbul ignore next */
+export interface ICollection {
+    find(query: any, sortKeys: any, count: number): BluebirdPromise<any>;
+    findOne(query: any): BluebirdPromise<any>;
+    update(query: any, updateQuery: any, options?: any): BluebirdPromise<any>;
+    remove(query: any): BluebirdPromise<any>;
+    insert(document: any): BluebirdPromise<any>;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/ICollectionFactory.d.ts b/themes/triangles/server/src/lib/storage/ICollectionFactory.d.ts
new file mode 100644
index 00000000..39eb42c7
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/ICollectionFactory.d.ts
@@ -0,0 +1,6 @@
+
+import { ICollection } from "./ICollection";
+
+export interface ICollectionFactory {
+    build(collectionName: string): ICollection;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/IUserDataStore.d.ts b/themes/triangles/server/src/lib/storage/IUserDataStore.d.ts
new file mode 100644
index 00000000..81df482a
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/IUserDataStore.d.ts
@@ -0,0 +1,21 @@
+import BluebirdPromise = require("bluebird");
+import { TOTPSecretDocument } from "./TOTPSecretDocument";
+import { U2FRegistrationDocument } from "./U2FRegistrationDocument";
+import { U2FRegistration } from "../../../types/U2FRegistration";
+import { TOTPSecret } from "../../../types/TOTPSecret";
+import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
+import { IdentityValidationDocument } from "./IdentityValidationDocument";
+
+export interface IUserDataStore {
+    saveU2FRegistration(userId: string, appId: string, registration: U2FRegistration): BluebirdPromise<void>;
+    retrieveU2FRegistration(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument>;
+
+    saveAuthenticationTrace(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void>;
+    retrieveLatestAuthenticationTraces(userId: string, count: number): BluebirdPromise<AuthenticationTraceDocument[]>;
+
+    produceIdentityValidationToken(userId: string, token: string, challenge: string, maxAge: number): BluebirdPromise<any>;
+    consumeIdentityValidationToken(token: string, challenge: string): BluebirdPromise<IdentityValidationDocument>;
+
+    saveTOTPSecret(userId: string, secret: TOTPSecret): BluebirdPromise<void>;
+    retrieveTOTPSecret(userId: string): BluebirdPromise<TOTPSecretDocument>;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/IdentityValidationDocument.d.ts b/themes/triangles/server/src/lib/storage/IdentityValidationDocument.d.ts
new file mode 100644
index 00000000..e7fd7b3f
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/IdentityValidationDocument.d.ts
@@ -0,0 +1,7 @@
+
+export interface IdentityValidationDocument {
+    userId: string;
+    token: string;
+    challenge: string;
+    maxDate: Date;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/TOTPSecretDocument.d.ts b/themes/triangles/server/src/lib/storage/TOTPSecretDocument.d.ts
new file mode 100644
index 00000000..a6c0bf9e
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/TOTPSecretDocument.d.ts
@@ -0,0 +1,6 @@
+import { TOTPSecret } from "../../../types/TOTPSecret";
+
+export interface TOTPSecretDocument {
+  userid: string;
+  secret: TOTPSecret;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/U2FRegistrationDocument.d.ts b/themes/triangles/server/src/lib/storage/U2FRegistrationDocument.d.ts
new file mode 100644
index 00000000..efec6cb1
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/U2FRegistrationDocument.d.ts
@@ -0,0 +1,8 @@
+
+import { U2FRegistration } from "../../../types/U2FRegistration";
+
+export interface U2FRegistrationDocument {
+  userId: string;
+  appId: string;
+  registration: U2FRegistration;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/UserDataStore.spec.ts b/themes/triangles/server/src/lib/storage/UserDataStore.spec.ts
new file mode 100644
index 00000000..66fb8546
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/UserDataStore.spec.ts
@@ -0,0 +1,264 @@
+
+import * as Assert from "assert";
+import * as Sinon from "sinon";
+import * as MockDate from "mockdate";
+import BluebirdPromise = require("bluebird");
+
+import { UserDataStore } from "./UserDataStore";
+import { TOTPSecret } from "../../../types/TOTPSecret";
+import { U2FRegistration } from "../../../types/U2FRegistration";
+import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
+import { CollectionStub } from "./CollectionStub.spec";
+import { CollectionFactoryStub } from "./CollectionFactoryStub.spec";
+
+describe("storage/UserDataStore", function () {
+  let factory: CollectionFactoryStub;
+  let collection: CollectionStub;
+  let userId: string;
+  let appId: string;
+  let totpSecret: TOTPSecret;
+  let u2fRegistration: U2FRegistration;
+
+  beforeEach(function () {
+    factory = new CollectionFactoryStub();
+    collection = new CollectionStub();
+
+    userId = "user";
+    appId = "https://myappId";
+
+    totpSecret = {
+      ascii: "abc",
+      base32: "ABCDKZLEFZGREJK",
+      otpauth_url: "totp://test",
+      google_auth_qr: "dummy",
+      hex: "dummy",
+      qr_code_ascii: "dummy",
+      qr_code_base32: "dummy",
+      qr_code_hex: "dummy"
+    };
+
+    u2fRegistration = {
+      keyHandle: "KEY_HANDLE",
+      publicKey: "publickey"
+    };
+  });
+
+  it("should correctly creates collections", function () {
+    new UserDataStore(factory);
+
+    Assert.equal(4, factory.buildStub.callCount);
+    Assert(factory.buildStub.calledWith("authentication_traces"));
+    Assert(factory.buildStub.calledWith("identity_validation_tokens"));
+    Assert(factory.buildStub.calledWith("u2f_registrations"));
+    Assert(factory.buildStub.calledWith("totp_secrets"));
+  });
+
+  describe("TOTP secrets collection", function () {
+    it("should save a totp secret", function () {
+      factory.buildStub.returns(collection);
+      collection.updateStub.returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.saveTOTPSecret(userId, totpSecret)
+        .then(function (doc) {
+          Assert(collection.updateStub.calledOnce);
+          Assert(collection.updateStub.calledWith({ userId: userId }, {
+            userId: userId,
+            secret: totpSecret
+          }, { upsert: true }));
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should retrieve a totp secret", function () {
+      factory.buildStub.returns(collection);
+      collection.findOneStub.withArgs().returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.retrieveTOTPSecret(userId)
+        .then(function (doc) {
+          Assert(collection.findOneStub.calledOnce);
+          Assert(collection.findOneStub.calledWith({ userId: userId }));
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+
+  describe("U2F secrets collection", function () {
+    it("should save a U2F secret", function () {
+      factory.buildStub.returns(collection);
+      collection.updateStub.returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.saveU2FRegistration(userId, appId, u2fRegistration)
+        .then(function (doc) {
+          Assert(collection.updateStub.calledOnce);
+          Assert(collection.updateStub.calledWith({
+            userId: userId,
+            appId: appId
+          }, {
+              userId: userId,
+              appId: appId,
+              registration: u2fRegistration
+            }, { upsert: true }));
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should retrieve a U2F secret", function () {
+      factory.buildStub.returns(collection);
+      collection.findOneStub.withArgs().returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.retrieveU2FRegistration(userId, appId)
+        .then(function (doc) {
+          Assert(collection.findOneStub.calledOnce);
+          Assert(collection.findOneStub.calledWith({
+            userId: userId,
+            appId: appId
+          }));
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+
+
+  describe("Regulator traces collection", function () {
+    it("should save a trace", function () {
+      factory.buildStub.returns(collection);
+      collection.insertStub.returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.saveAuthenticationTrace(userId, true)
+        .then(function (doc) {
+          Assert(collection.insertStub.calledOnce);
+          Assert(collection.insertStub.calledWith({
+            userId: userId,
+            date: Sinon.match.date,
+            isAuthenticationSuccessful: true
+          }));
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    function should_retrieve_latest_authentication_traces(count: number) {
+      factory.buildStub.returns(collection);
+      collection.findStub.withArgs().returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      return dataStore.retrieveLatestAuthenticationTraces(userId, count)
+        .then(function (doc: AuthenticationTraceDocument[]) {
+          Assert(collection.findStub.calledOnce);
+          Assert(collection.findStub.calledWith({
+            userId: userId,
+          }, { date: -1 }, count));
+          return BluebirdPromise.resolve();
+        });
+    }
+
+    it("should retrieve 3 latest failed authentication traces", function () {
+      should_retrieve_latest_authentication_traces(3);
+    });
+  });
+
+
+  describe("Identity validation collection", function () {
+    it("should save a identity validation token", function () {
+      factory.buildStub.returns(collection);
+      collection.insertStub.returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+      const maxAge = 400;
+      const token = "TOKEN";
+      const challenge = "CHALLENGE";
+
+      return dataStore.produceIdentityValidationToken(userId, token, challenge, maxAge)
+        .then(function (doc) {
+          Assert(collection.insertStub.calledOnce);
+          Assert(collection.insertStub.calledWith({
+            userId: userId,
+            token: token,
+            challenge: challenge,
+            maxDate: Sinon.match.date
+          }));
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should consume an identity token successfully", function () {
+      factory.buildStub.returns(collection);
+
+      MockDate.set(100);
+
+      const token = "TOKEN";
+      const challenge = "CHALLENGE";
+
+      collection.findOneStub.withArgs().returns(BluebirdPromise.resolve({
+        userId: "USER",
+        token: token,
+        challenge: challenge,
+        maxDate: new Date()
+      }));
+      collection.removeStub.returns(BluebirdPromise.resolve());
+
+      const dataStore = new UserDataStore(factory);
+
+      MockDate.set(80);
+
+      return dataStore.consumeIdentityValidationToken(token, challenge)
+        .then(function (doc) {
+          MockDate.reset();
+          Assert(collection.findOneStub.calledOnce);
+          Assert(collection.findOneStub.calledWith({
+            token: token,
+            challenge: challenge
+          }));
+
+          Assert(collection.removeStub.calledOnce);
+          Assert(collection.removeStub.calledWith({
+            token: token,
+            challenge: challenge
+          }));
+          return BluebirdPromise.resolve();
+        });
+    });
+
+    it("should consume an expired identity token", function () {
+      factory.buildStub.returns(collection);
+
+      MockDate.set(0);
+
+      const token = "TOKEN";
+      const challenge = "CHALLENGE";
+
+      collection.findOneStub.withArgs().returns(BluebirdPromise.resolve({
+        userId: "USER",
+        token: token,
+        challenge: challenge,
+        maxDate: new Date()
+      }));
+
+      const dataStore = new UserDataStore(factory);
+
+      MockDate.set(80000);
+
+      return dataStore.consumeIdentityValidationToken(token, challenge)
+        .then(function () { return BluebirdPromise.reject(new Error("should not be here")); })
+        .catch(function () {
+          MockDate.reset();
+          Assert(collection.findOneStub.calledOnce);
+          Assert(collection.findOneStub.calledWith({
+            token: token,
+            challenge: challenge
+          }));
+          return BluebirdPromise.resolve();
+        });
+    });
+  });
+});
diff --git a/themes/triangles/server/src/lib/storage/UserDataStore.ts b/themes/triangles/server/src/lib/storage/UserDataStore.ts
new file mode 100644
index 00000000..27b0cddb
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/UserDataStore.ts
@@ -0,0 +1,143 @@
+import * as BluebirdPromise from "bluebird";
+import * as path from "path";
+import { IUserDataStore } from "./IUserDataStore";
+import { ICollection } from "./ICollection";
+import { ICollectionFactory } from "./ICollectionFactory";
+import { TOTPSecretDocument } from "./TOTPSecretDocument";
+import { U2FRegistrationDocument } from "./U2FRegistrationDocument";
+import { U2FRegistration } from "../../../types/U2FRegistration";
+import { TOTPSecret } from "../../../types/TOTPSecret";
+import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
+import { IdentityValidationDocument } from "./IdentityValidationDocument";
+
+// Constants
+
+const IDENTITY_VALIDATION_TOKENS_COLLECTION_NAME = "identity_validation_tokens";
+const AUTHENTICATION_TRACES_COLLECTION_NAME = "authentication_traces";
+
+const U2F_REGISTRATIONS_COLLECTION_NAME = "u2f_registrations";
+const TOTP_SECRETS_COLLECTION_NAME = "totp_secrets";
+
+
+export interface U2FRegistrationKey {
+  userId: string;
+  appId: string;
+}
+
+// Source
+
+export class UserDataStore implements IUserDataStore {
+  private u2fSecretCollection: ICollection;
+  private identityCheckTokensCollection: ICollection;
+  private authenticationTracesCollection: ICollection;
+  private totpSecretCollection: ICollection;
+
+  private collectionFactory: ICollectionFactory;
+
+  constructor(collectionFactory: ICollectionFactory) {
+    this.collectionFactory = collectionFactory;
+
+    this.u2fSecretCollection = this.collectionFactory.build(U2F_REGISTRATIONS_COLLECTION_NAME);
+    this.identityCheckTokensCollection = this.collectionFactory.build(IDENTITY_VALIDATION_TOKENS_COLLECTION_NAME);
+    this.authenticationTracesCollection = this.collectionFactory.build(AUTHENTICATION_TRACES_COLLECTION_NAME);
+    this.totpSecretCollection = this.collectionFactory.build(TOTP_SECRETS_COLLECTION_NAME);
+  }
+
+  saveU2FRegistration(userId: string, appId: string, registration: U2FRegistration): BluebirdPromise<void> {
+    const newDocument: U2FRegistrationDocument = {
+      userId: userId,
+      appId: appId,
+      registration: registration
+    };
+
+    const filter: U2FRegistrationKey = {
+      userId: userId,
+      appId: appId
+    };
+
+    return this.u2fSecretCollection.update(filter, newDocument, { upsert: true });
+  }
+
+  retrieveU2FRegistration(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument> {
+    const filter: U2FRegistrationKey = {
+      userId: userId,
+      appId: appId
+    };
+    return this.u2fSecretCollection.findOne(filter);
+  }
+
+  saveAuthenticationTrace(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
+    const newDocument: AuthenticationTraceDocument = {
+      userId: userId,
+      date: new Date(),
+      isAuthenticationSuccessful: isAuthenticationSuccessful,
+    };
+
+    return this.authenticationTracesCollection.insert(newDocument);
+  }
+
+  retrieveLatestAuthenticationTraces(userId: string, count: number): BluebirdPromise<AuthenticationTraceDocument[]> {
+    const q = {
+      userId: userId
+    };
+
+    return this.authenticationTracesCollection.find(q, { date: -1 }, count);
+  }
+
+  produceIdentityValidationToken(userId: string, token: string, challenge: string, maxAge: number): BluebirdPromise<any> {
+    const newDocument: IdentityValidationDocument = {
+      userId: userId,
+      token: token,
+      challenge: challenge,
+      maxDate: new Date(new Date().getTime() + maxAge)
+    };
+
+    return this.identityCheckTokensCollection.insert(newDocument);
+  }
+
+  consumeIdentityValidationToken(token: string, challenge: string): BluebirdPromise<IdentityValidationDocument> {
+    const that = this;
+    const filter = {
+      token: token,
+      challenge: challenge
+    };
+
+    let identityValidationDocument: IdentityValidationDocument;
+
+    return this.identityCheckTokensCollection.findOne(filter)
+      .then(function (doc: IdentityValidationDocument) {
+        if (!doc) {
+          return BluebirdPromise.reject(new Error("Registration token does not exist"));
+        }
+
+        identityValidationDocument = doc;
+        const current_date = new Date();
+        if (current_date > doc.maxDate)
+          return BluebirdPromise.reject(new Error("Registration token is not valid anymore"));
+
+        return that.identityCheckTokensCollection.remove(filter);
+      })
+      .then(() => {
+        return BluebirdPromise.resolve(identityValidationDocument);
+      });
+  }
+
+  saveTOTPSecret(userId: string, secret: TOTPSecret): BluebirdPromise<void> {
+    const doc = {
+      userId: userId,
+      secret: secret
+    };
+
+    const filter = {
+      userId: userId
+    };
+    return this.totpSecretCollection.update(filter, doc, { upsert: true });
+  }
+
+  retrieveTOTPSecret(userId: string): BluebirdPromise<TOTPSecretDocument> {
+    const filter = {
+      userId: userId
+    };
+    return this.totpSecretCollection.findOne(filter);
+  }
+}
diff --git a/themes/triangles/server/src/lib/storage/UserDataStoreStub.spec.ts b/themes/triangles/server/src/lib/storage/UserDataStoreStub.spec.ts
new file mode 100644
index 00000000..5ea27a2d
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/UserDataStoreStub.spec.ts
@@ -0,0 +1,64 @@
+import Sinon = require("sinon");
+import BluebirdPromise = require("bluebird");
+
+import { TOTPSecretDocument } from "./TOTPSecretDocument";
+import { U2FRegistrationDocument } from "./U2FRegistrationDocument";
+import { U2FRegistration } from "../../../types/U2FRegistration";
+import { TOTPSecret } from "../../../types/TOTPSecret";
+import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
+import { IdentityValidationDocument } from "./IdentityValidationDocument";
+import { IUserDataStore } from "./IUserDataStore";
+
+export class UserDataStoreStub implements IUserDataStore {
+    saveU2FRegistrationStub: Sinon.SinonStub;
+    retrieveU2FRegistrationStub: Sinon.SinonStub;
+    saveAuthenticationTraceStub: Sinon.SinonStub;
+    retrieveLatestAuthenticationTracesStub: Sinon.SinonStub;
+    produceIdentityValidationTokenStub: Sinon.SinonStub;
+    consumeIdentityValidationTokenStub: Sinon.SinonStub;
+    saveTOTPSecretStub: Sinon.SinonStub;
+    retrieveTOTPSecretStub: Sinon.SinonStub;
+
+    constructor() {
+        this.saveU2FRegistrationStub = Sinon.stub();
+        this.retrieveU2FRegistrationStub = Sinon.stub();
+        this.saveAuthenticationTraceStub = Sinon.stub();
+        this.retrieveLatestAuthenticationTracesStub = Sinon.stub();
+        this.produceIdentityValidationTokenStub = Sinon.stub();
+        this.consumeIdentityValidationTokenStub = Sinon.stub();
+        this.saveTOTPSecretStub = Sinon.stub();
+        this.retrieveTOTPSecretStub = Sinon.stub();
+    }
+
+    saveU2FRegistration(userId: string, appId: string, registration: U2FRegistration): BluebirdPromise<void> {
+        return this.saveU2FRegistrationStub(userId, appId, registration);
+    }
+
+    retrieveU2FRegistration(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument> {
+        return this.retrieveU2FRegistrationStub(userId, appId);
+    }
+
+    saveAuthenticationTrace(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
+        return this.saveAuthenticationTraceStub(userId, isAuthenticationSuccessful);
+    }
+
+    retrieveLatestAuthenticationTraces(userId: string, count: number): BluebirdPromise<AuthenticationTraceDocument[]> {
+        return this.retrieveLatestAuthenticationTracesStub(userId, count);
+    }
+
+    produceIdentityValidationToken(userId: string, token: string, challenge: string, maxAge: number): BluebirdPromise<any> {
+        return this.produceIdentityValidationTokenStub(userId, token, challenge, maxAge);
+    }
+
+    consumeIdentityValidationToken(token: string, challenge: string): BluebirdPromise<IdentityValidationDocument> {
+        return this.consumeIdentityValidationTokenStub(token, challenge);
+    }
+
+    saveTOTPSecret(userId: string, secret: TOTPSecret): BluebirdPromise<void> {
+        return this.saveTOTPSecretStub(userId, secret);
+    }
+
+    retrieveTOTPSecret(userId: string): BluebirdPromise<TOTPSecretDocument> {
+        return this.retrieveTOTPSecretStub(userId);
+    }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/mongo/MongoCollection.spec.ts b/themes/triangles/server/src/lib/storage/mongo/MongoCollection.spec.ts
new file mode 100644
index 00000000..74a773a1
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/mongo/MongoCollection.spec.ts
@@ -0,0 +1,110 @@
+import Assert = require("assert");
+import Sinon = require("sinon");
+import MongoDB = require("mongodb");
+import BluebirdPromise = require("bluebird");
+import { MongoClientStub } from "../../connectors/mongo/MongoClientStub.spec";
+import { MongoCollection } from "./MongoCollection";
+
+describe("storage/mongo/MongoCollection", function () {
+  let mongoCollectionStub: any;
+  let mongoClientStub: MongoClientStub;
+  let findStub: Sinon.SinonStub;
+  let findOneStub: Sinon.SinonStub;
+  let insertOneStub: Sinon.SinonStub;
+  let updateStub: Sinon.SinonStub;
+  let removeStub: Sinon.SinonStub;
+  let countStub: Sinon.SinonStub;
+  const COLLECTION_NAME = "collection";
+
+  before(function () {
+    mongoClientStub = new MongoClientStub();
+    mongoCollectionStub = Sinon.createStubInstance(require("mongodb").Collection as any);
+    findStub = mongoCollectionStub.find as Sinon.SinonStub;
+    findOneStub = mongoCollectionStub.findOne as Sinon.SinonStub;
+    insertOneStub = mongoCollectionStub.insertOne as Sinon.SinonStub;
+    updateStub = mongoCollectionStub.update as Sinon.SinonStub;
+    removeStub = mongoCollectionStub.remove as Sinon.SinonStub;
+    countStub = mongoCollectionStub.count as Sinon.SinonStub;
+    mongoClientStub.collectionStub.returns(
+      BluebirdPromise.resolve(mongoCollectionStub)
+    );
+  });
+
+  describe("find", function () {
+    it("should find a document in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      findStub.returns({
+        sort: Sinon.stub().returns({
+          limit: Sinon.stub().returns({
+            toArray: Sinon.stub().returns(BluebirdPromise.resolve([]))
+          })
+        })
+      });
+
+      return collection.find({ key: "KEY" })
+        .then(function () {
+          Assert(findStub.calledWith({ key: "KEY" }));
+        });
+    });
+  });
+
+  describe("findOne", function () {
+    it("should find one document in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      findOneStub.returns(BluebirdPromise.resolve({}));
+
+      return collection.findOne({ key: "KEY" })
+        .then(function () {
+          Assert(findOneStub.calledWith({ key: "KEY" }));
+        });
+    });
+  });
+
+  describe("insert", function () {
+    it("should insert a document in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      insertOneStub.returns(BluebirdPromise.resolve({}));
+
+      return collection.insert({ key: "KEY" })
+        .then(function () {
+          Assert(insertOneStub.calledWith({ key: "KEY" }));
+        });
+    });
+  });
+
+  describe("update", function () {
+    it("should update a document in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      updateStub.returns(BluebirdPromise.resolve({}));
+
+      return collection.update({ key: "KEY" }, { key: "KEY", value: 1 })
+        .then(function () {
+          Assert(updateStub.calledWith({ key: "KEY" }, { key: "KEY", value: 1 }));
+        });
+    });
+  });
+
+  describe("remove", function () {
+    it("should remove a document in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      removeStub.returns(BluebirdPromise.resolve({}));
+
+      return collection.remove({ key: "KEY" })
+        .then(function () {
+          Assert(removeStub.calledWith({ key: "KEY" }));
+        });
+    });
+  });
+
+  describe("count", function () {
+    it("should count documents in the collection", function () {
+      const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
+      countStub.returns(BluebirdPromise.resolve({}));
+
+      return collection.count({ key: "KEY" })
+        .then(function () {
+          Assert(countStub.calledWith({ key: "KEY" }));
+        });
+    });
+  });
+});
diff --git a/themes/triangles/server/src/lib/storage/mongo/MongoCollection.ts b/themes/triangles/server/src/lib/storage/mongo/MongoCollection.ts
new file mode 100644
index 00000000..9771389f
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/mongo/MongoCollection.ts
@@ -0,0 +1,50 @@
+import Bluebird = require("bluebird");
+import { ICollection } from "../ICollection";
+import MongoDB = require("mongodb");
+import { IMongoClient } from "../../connectors/mongo/IMongoClient";
+
+
+export class MongoCollection implements ICollection {
+  private mongoClient: IMongoClient;
+  private collectionName: string;
+
+  constructor(collectionName: string, mongoClient: IMongoClient) {
+    this.collectionName = collectionName;
+    this.mongoClient = mongoClient;
+  }
+
+  private collection(): Bluebird<MongoDB.Collection> {
+    return this.mongoClient.collection(this.collectionName);
+  }
+
+  find(query: any, sortKeys?: any, count?: number): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.find(query).sort(sortKeys).limit(count))
+      .then((query) => query.toArray());
+  }
+
+  findOne(query: any): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.findOne(query));
+  }
+
+  update(query: any, updateQuery: any, options?: any): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.update(query, updateQuery, options));
+  }
+
+  remove(query: any): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.remove(query));
+  }
+
+  insert(document: any): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.insertOne(document));
+  }
+
+  count(query: any): Bluebird<any> {
+    return this.collection()
+      .then((collection) => collection.count(query));
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/mongo/MongoCollectionFactory.spec.ts b/themes/triangles/server/src/lib/storage/mongo/MongoCollectionFactory.spec.ts
new file mode 100644
index 00000000..bd959cac
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/mongo/MongoCollectionFactory.spec.ts
@@ -0,0 +1,21 @@
+import Assert = require("assert");
+import Sinon = require("sinon");
+import { MongoClientStub } from "../../connectors/mongo/MongoClientStub.spec";
+import { MongoCollectionFactory } from "./MongoCollectionFactory";
+
+describe("storage/mongo/MongoCollectionFactory", function () {
+  let mongoClient: MongoClientStub;
+
+  before(function() {
+    mongoClient = new MongoClientStub();
+  });
+
+  describe("create", function () {
+    it("should create a collection", function () {
+      const COLLECTION_NAME = "COLLECTION_NAME";
+
+      const factory = new MongoCollectionFactory(mongoClient);
+      Assert(factory.build(COLLECTION_NAME));
+    });
+  });
+});
diff --git a/themes/triangles/server/src/lib/storage/mongo/MongoCollectionFactory.ts b/themes/triangles/server/src/lib/storage/mongo/MongoCollectionFactory.ts
new file mode 100644
index 00000000..14a8262c
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/mongo/MongoCollectionFactory.ts
@@ -0,0 +1,19 @@
+import BluebirdPromise = require("bluebird");
+import { ICollection } from "../ICollection";
+import { ICollectionFactory } from "../ICollectionFactory";
+import { MongoCollection } from "./MongoCollection";
+import path = require("path");
+import MongoDB = require("mongodb");
+import { IMongoClient } from "../../connectors/mongo/IMongoClient";
+
+export class MongoCollectionFactory implements ICollectionFactory {
+  private mongoClient: IMongoClient;
+
+  constructor(mongoClient: IMongoClient) {
+    this.mongoClient = mongoClient;
+  }
+
+  build(collectionName: string): ICollection {
+    return new MongoCollection(collectionName, this.mongoClient);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/nedb/NedbCollection.spec.ts b/themes/triangles/server/src/lib/storage/nedb/NedbCollection.spec.ts
new file mode 100644
index 00000000..a69962b6
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/nedb/NedbCollection.spec.ts
@@ -0,0 +1,136 @@
+import Sinon = require("sinon");
+import Assert = require("assert");
+
+import { NedbCollection } from "./NedbCollection";
+
+describe("storage/nedb/NedbCollection", function () {
+  describe("insert", function () {
+    it("should insert one entry", function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      const collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou" });
+
+      return collection.count({}).then(function (count: number) {
+        Assert.equal(1, count);
+      });
+    });
+
+    it("should insert three entries", function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      const collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou" });
+      collection.insert({ key: "hello" });
+      collection.insert({ key: "hey" });
+
+      return collection.count({}).then(function (count: number) {
+        Assert.equal(3, count);
+      });
+    });
+  });
+
+  describe("find", function () {
+    let collection: NedbCollection;
+    before(function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou", value: 1 });
+      collection.insert({ key: "hello" });
+      collection.insert({ key: "hey" });
+      collection.insert({ key: "coucou", value: 2 });
+    });
+
+    it("should find one hello", function () {
+      return collection.find({ key: "hello" }, { key: 1 })
+        .then(function (docs: { key: string }[]) {
+          Assert.equal(1, docs.length);
+          Assert(docs[0].key == "hello");
+        });
+    });
+
+    it("should find two coucou", function () {
+      return collection.find({ key: "coucou" }, { value: 1 })
+        .then(function (docs: { value: number }[]) {
+          Assert.equal(2, docs.length);
+        });
+    });
+  });
+
+  describe("findOne", function () {
+    let collection: NedbCollection;
+    before(function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou", value: 1 });
+      collection.insert({ key: "coucou", value: 1 });
+      collection.insert({ key: "coucou", value: 1 });
+      collection.insert({ key: "coucou", value: 1 });
+    });
+
+    it("should find two coucou", function () {
+      const doc = { key: "coucou", value: 1 };
+      return collection.count(doc)
+        .then(function (count: number) {
+          Assert.equal(4, count);
+          return collection.findOne(doc);
+        });
+    });
+  });
+
+  describe("update", function () {
+    let collection: NedbCollection;
+    before(function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou", value: 1 });
+    });
+
+    it("should update the value", function () {
+      return collection.update({ key: "coucou" }, { key: "coucou", value: 2 }, { multi: true })
+        .then(function () {
+          return collection.find({ key: "coucou" });
+        })
+        .then(function (docs: { key: string, value: number }[]) {
+          Assert.equal(1, docs.length);
+          Assert.equal(2, docs[0].value);
+        });
+    });
+  });
+
+  describe("update", function () {
+    let collection: NedbCollection;
+    before(function () {
+      const nedbOptions = {
+        inMemoryOnly: true
+      };
+      collection = new NedbCollection(nedbOptions);
+
+      collection.insert({ key: "coucou" });
+      collection.insert({ key: "hello" });
+    });
+
+    it("should update the value", function () {
+      return collection.remove({ key: "coucou" })
+        .then(function () {
+          return collection.count({});
+        })
+        .then(function (count: number) {
+          Assert.equal(1, count);
+        });
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/nedb/NedbCollection.ts b/themes/triangles/server/src/lib/storage/nedb/NedbCollection.ts
new file mode 100644
index 00000000..88a93ad0
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/nedb/NedbCollection.ts
@@ -0,0 +1,47 @@
+import BluebirdPromise = require("bluebird");
+import { ICollection } from "../ICollection";
+import Nedb = require("nedb");
+
+declare module "nedb" {
+    export class NedbAsync extends Nedb {
+        constructor(pathOrOptions?: string | Nedb.DataStoreOptions);
+        updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): BluebirdPromise<any>;
+        findOneAsync<T>(query: any): BluebirdPromise<T>;
+        insertAsync<T>(newDoc: T): BluebirdPromise<any>;
+        removeAsync(query: any): BluebirdPromise<any>;
+        countAsync(query: any): BluebirdPromise<number>;
+    }
+}
+
+export class NedbCollection implements ICollection {
+  private collection: Nedb.NedbAsync;
+
+  constructor(options: Nedb.DataStoreOptions) {
+    this.collection = BluebirdPromise.promisifyAll(new Nedb(options)) as Nedb.NedbAsync;
+  }
+
+  find(query: any, sortKeys?: any, count?: number): BluebirdPromise<any> {
+    const q = this.collection.find(query).sort(sortKeys).limit(count);
+    return BluebirdPromise.promisify(q.exec, { context: q })();
+  }
+
+  findOne(query: any): BluebirdPromise<any> {
+    return this.collection.findOneAsync(query);
+  }
+
+  update(query: any, updateQuery: any, options?: any): BluebirdPromise<any> {
+    return this.collection.updateAsync(query, updateQuery, options);
+  }
+
+  remove(query: any): BluebirdPromise<any> {
+    return this.collection.removeAsync(query);
+  }
+
+  insert(document: any): BluebirdPromise<any> {
+    return this.collection.insertAsync(document);
+  }
+
+  count(query: any): BluebirdPromise<number> {
+    return this.collection.countAsync(query);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/nedb/NedbCollectionFactory.spec.ts b/themes/triangles/server/src/lib/storage/nedb/NedbCollectionFactory.spec.ts
new file mode 100644
index 00000000..da90c661
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/nedb/NedbCollectionFactory.spec.ts
@@ -0,0 +1,16 @@
+import Sinon = require("sinon");
+import Assert = require("assert");
+
+import { NedbCollectionFactory } from "./NedbCollectionFactory";
+
+describe("storage/nedb/NedbCollectionFactory", function() {
+  it("should create a nedb collection", function() {
+    const nedbOptions = {
+      inMemoryOnly: true
+    };
+    const factory = new NedbCollectionFactory(nedbOptions);
+
+    const collection = factory.build("mycollection");
+    Assert(collection);
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/storage/nedb/NedbCollectionFactory.ts b/themes/triangles/server/src/lib/storage/nedb/NedbCollectionFactory.ts
new file mode 100644
index 00000000..49c4dc85
--- /dev/null
+++ b/themes/triangles/server/src/lib/storage/nedb/NedbCollectionFactory.ts
@@ -0,0 +1,28 @@
+import { ICollection } from "../ICollection";
+import { ICollectionFactory } from "../ICollectionFactory";
+import { NedbCollection } from "./NedbCollection";
+import path = require("path");
+import Nedb = require("nedb");
+
+export interface NedbOptions {
+  inMemoryOnly?: boolean;
+  directory?: string;
+}
+
+export class NedbCollectionFactory implements ICollectionFactory {
+  private options: Nedb.DataStoreOptions;
+
+  constructor(options: Nedb.DataStoreOptions) {
+    this.options = options;
+  }
+
+  build(collectionName: string): ICollection {
+    const datastoreOptions: Nedb.DataStoreOptions = {
+      inMemoryOnly: this.options.inMemoryOnly || false,
+      autoload: true,
+      filename: (this.options.filename) ? path.resolve(this.options.filename, collectionName) : undefined
+    };
+
+    return new NedbCollection(datastoreOptions);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/stubs/express.spec.ts b/themes/triangles/server/src/lib/stubs/express.spec.ts
new file mode 100644
index 00000000..48f15d7e
--- /dev/null
+++ b/themes/triangles/server/src/lib/stubs/express.spec.ts
@@ -0,0 +1,103 @@
+
+import sinon = require("sinon");
+import express = require("express");
+
+export interface RequestMock {
+    app?: any;
+    body?: any;
+    session?: any;
+    headers?: any;
+    get?: any;
+    query?: any;
+    originalUrl: string;
+}
+
+export interface ResponseMock {
+    send: sinon.SinonStub | sinon.SinonSpy;
+    sendStatus: sinon.SinonStub;
+    sendFile: sinon.SinonStub;
+    sendfile: sinon.SinonStub;
+    status: sinon.SinonStub | sinon.SinonSpy;
+    json: sinon.SinonStub | sinon.SinonSpy;
+    links: sinon.SinonStub;
+    jsonp: sinon.SinonStub;
+    download: sinon.SinonStub;
+    contentType: sinon.SinonStub;
+    type: sinon.SinonStub;
+    format: sinon.SinonStub;
+    attachment: sinon.SinonStub;
+    set: sinon.SinonStub;
+    header: sinon.SinonStub;
+    headersSent: boolean;
+    get: sinon.SinonStub;
+    clearCookie: sinon.SinonStub;
+    cookie: sinon.SinonStub;
+    location: sinon.SinonStub;
+    redirect: sinon.SinonStub | sinon.SinonSpy;
+    render: sinon.SinonStub | sinon.SinonSpy;
+    locals: sinon.SinonStub;
+    charset: string;
+    vary: sinon.SinonStub;
+    app: any;
+    write: sinon.SinonStub;
+    writeContinue: sinon.SinonStub;
+    writeHead: sinon.SinonStub;
+    statusCode: number;
+    statusMessage: string;
+    setHeader: sinon.SinonStub;
+    setTimeout: sinon.SinonStub;
+    sendDate: boolean;
+    getHeader: sinon.SinonStub;
+}
+
+export function RequestMock(): RequestMock {
+    return {
+        originalUrl: "/non-api/xxx",
+        app: {
+            get: sinon.stub()
+        },
+        headers: {
+            "x-forwarded-for": "127.0.0.1"
+        },
+        session: {}
+    };
+}
+export function ResponseMock(): ResponseMock {
+    return {
+        send: sinon.stub(),
+        status: sinon.stub(),
+        json: sinon.stub(),
+        sendStatus: sinon.stub(),
+        links: sinon.stub(),
+        jsonp: sinon.stub(),
+        sendFile: sinon.stub(),
+        sendfile: sinon.stub(),
+        download: sinon.stub(),
+        contentType: sinon.stub(),
+        type: sinon.stub(),
+        format: sinon.stub(),
+        attachment: sinon.stub(),
+        set: sinon.stub(),
+        header: sinon.stub(),
+        headersSent: true,
+        get: sinon.stub(),
+        clearCookie: sinon.stub(),
+        cookie: sinon.stub(),
+        location: sinon.stub(),
+        redirect: sinon.stub(),
+        render: sinon.stub(),
+        locals: sinon.stub(),
+        charset: "utf-8",
+        vary: sinon.stub(),
+        app: sinon.stub(),
+        write: sinon.stub(),
+        writeContinue: sinon.stub(),
+        writeHead: sinon.stub(),
+        statusCode: 200,
+        statusMessage: "message",
+        setHeader: sinon.stub(),
+        setTimeout: sinon.stub(),
+        sendDate: true,
+        getHeader: sinon.stub()
+    };
+}
diff --git a/themes/triangles/server/src/lib/stubs/ldapjs.spec.ts b/themes/triangles/server/src/lib/stubs/ldapjs.spec.ts
new file mode 100644
index 00000000..045c0e11
--- /dev/null
+++ b/themes/triangles/server/src/lib/stubs/ldapjs.spec.ts
@@ -0,0 +1,50 @@
+
+import Sinon = require("sinon");
+
+export class LdapjsMock {
+    createClientStub: sinon.SinonStub;
+
+    constructor() {
+        this.createClientStub = Sinon.stub();
+    }
+
+    createClient(params: any) {
+        return this.createClientStub(params);
+    }
+}
+
+export class LdapjsClientMock {
+    bindStub: sinon.SinonStub;
+    unbindStub: sinon.SinonStub;
+    searchStub: sinon.SinonStub;
+    modifyStub: sinon.SinonStub;
+    onStub: sinon.SinonStub;
+
+    constructor() {
+        this.bindStub = Sinon.stub();
+        this.unbindStub = Sinon.stub();
+        this.searchStub = Sinon.stub();
+        this.modifyStub = Sinon.stub();
+        this.onStub = Sinon.stub();
+    }
+
+    bind() {
+        return this.bindStub();
+    }
+
+    unbind() {
+        return this.unbindStub();
+    }
+
+    search() {
+        return this.searchStub();
+    }
+
+    modify() {
+        return this.modifyStub();
+    }
+
+    on() {
+        return this.onStub();
+    }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/stubs/speakeasy.spec.ts b/themes/triangles/server/src/lib/stubs/speakeasy.spec.ts
new file mode 100644
index 00000000..023614dc
--- /dev/null
+++ b/themes/triangles/server/src/lib/stubs/speakeasy.spec.ts
@@ -0,0 +1,7 @@
+
+import sinon = require("sinon");
+
+export = {
+    totp: sinon.stub(),
+    generateSecret: sinon.stub()
+};
diff --git a/themes/triangles/server/src/lib/stubs/u2f.spec.ts b/themes/triangles/server/src/lib/stubs/u2f.spec.ts
new file mode 100644
index 00000000..234b28c1
--- /dev/null
+++ b/themes/triangles/server/src/lib/stubs/u2f.spec.ts
@@ -0,0 +1,16 @@
+
+import sinon = require("sinon");
+
+export interface U2FMock {
+    request: sinon.SinonStub;
+    checkSignature: sinon.SinonStub;
+    checkRegistration: sinon.SinonStub;
+}
+
+export function U2FMock(): U2FMock {
+    return {
+        request: sinon.stub(),
+        checkSignature: sinon.stub(),
+        checkRegistration: sinon.stub()
+    };
+}
diff --git a/themes/triangles/server/src/lib/utils/HashGenerator.spec.ts b/themes/triangles/server/src/lib/utils/HashGenerator.spec.ts
new file mode 100644
index 00000000..f19619a6
--- /dev/null
+++ b/themes/triangles/server/src/lib/utils/HashGenerator.spec.ts
@@ -0,0 +1,18 @@
+import Assert = require("assert");
+import { HashGenerator } from "./HashGenerator";
+
+describe("utils/HashGenerator", function () {
+  it("should compute correct ssha512 (password)", function () {
+    return HashGenerator.ssha512("password", 500000, "jgiCMRyGXzoqpxS3")
+      .then(function (hash: string) {
+        Assert.equal(hash, "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/");
+      });
+  });
+
+  it("should compute correct ssha512 (test)", function () {
+    return HashGenerator.ssha512("test", 500000, "abcdefghijklmnop")
+      .then(function (hash: string) {
+        Assert.equal(hash, "{CRYPT}$6$rounds=500000$abcdefghijklmnop$sTlNGf0VO/HTQIOXemmaBbV28HUch/qhWOA1/4dsDj6CDQYhUgXbYSPL6gccAsWMr2zD5fFWwhKmPdG.yxphs.");
+      });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/utils/HashGenerator.ts b/themes/triangles/server/src/lib/utils/HashGenerator.ts
new file mode 100644
index 00000000..e67de32b
--- /dev/null
+++ b/themes/triangles/server/src/lib/utils/HashGenerator.ts
@@ -0,0 +1,23 @@
+import BluebirdPromise = require("bluebird");
+import RandomString = require("randomstring");
+import Util = require("util");
+const crypt = require("crypt3");
+
+export class HashGenerator {
+  static ssha512(
+    password: string,
+    rounds: number = 500000,
+    salt?: string): BluebirdPromise<string> {
+    const saltSize = 16;
+    // $6 means SHA512
+    const _salt = Util.format("$6$rounds=%d$%s", rounds,
+      (salt) ? salt : RandomString.generate(16));
+
+    const cryptAsync = BluebirdPromise.promisify<string, string, string>(crypt);
+
+    return cryptAsync(password, _salt)
+      .then(function (hash: string) {
+        return BluebirdPromise.resolve(Util.format("{CRYPT}%s", hash));
+      });
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/utils/ObjectCloner.ts b/themes/triangles/server/src/lib/utils/ObjectCloner.ts
new file mode 100644
index 00000000..3e125d74
--- /dev/null
+++ b/themes/triangles/server/src/lib/utils/ObjectCloner.ts
@@ -0,0 +1,6 @@
+
+export class ObjectCloner {
+  static clone(obj: any): any {
+    return JSON.parse(JSON.stringify(obj));
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/utils/SafeRedirection.spec.ts b/themes/triangles/server/src/lib/utils/SafeRedirection.spec.ts
new file mode 100644
index 00000000..4126949f
--- /dev/null
+++ b/themes/triangles/server/src/lib/utils/SafeRedirection.spec.ts
@@ -0,0 +1,33 @@
+import Assert = require("assert");
+import Sinon = require("sinon");
+import { SafeRedirector } from "./SafeRedirection";
+
+describe("web_server/middlewares/SafeRedirection", () => {
+  describe("Url is in protected domain", () => {
+    before(() => {
+      this.redirector = new SafeRedirector("example.com");
+      this.res = {redirect: Sinon.stub()};
+    });
+
+    it("should redirect to provided url", () => {
+      this.redirector.redirectOrElse(this.res,
+        "https://mysubdomain.example.com:8080/abc",
+        "https://authelia.example.com");
+      Assert(this.res.redirect.calledWith("https://mysubdomain.example.com:8080/abc"));
+    });
+
+    it("should redirect to default url when wrong domain", () => {
+      this.redirector.redirectOrElse(this.res,
+        "https://mysubdomain.domain.rtf:8080/abc",
+        "https://authelia.example.com");
+      Assert(this.res.redirect.calledWith("https://authelia.example.com"));
+    });
+
+    it("should redirect to default url when not terminating by domain", () => {
+      this.redirector.redirectOrElse(this.res,
+        "https://mysubdomain.example.com.rtf:8080/abc",
+        "https://authelia.example.com");
+      Assert(this.res.redirect.calledWith("https://authelia.example.com"));
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/utils/SafeRedirection.ts b/themes/triangles/server/src/lib/utils/SafeRedirection.ts
new file mode 100644
index 00000000..9e6a32e0
--- /dev/null
+++ b/themes/triangles/server/src/lib/utils/SafeRedirection.ts
@@ -0,0 +1,22 @@
+import Express = require("express");
+import { DomainExtractor } from "../../../../shared/DomainExtractor";
+import { BelongToDomain } from "../../../../shared/BelongToDomain";
+
+
+export class SafeRedirector {
+  private domain: string;
+
+  constructor(domain: string) {
+    this.domain = domain;
+  }
+
+  redirectOrElse(
+    res: Express.Response,
+    url: string,
+    defaultUrl: string): void {
+    if (BelongToDomain(url, this.domain)) {
+        res.redirect(url);
+      }
+      res.redirect(defaultUrl);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/utils/URLDecomposer.spec.ts b/themes/triangles/server/src/lib/utils/URLDecomposer.spec.ts
new file mode 100644
index 00000000..cbb03873
--- /dev/null
+++ b/themes/triangles/server/src/lib/utils/URLDecomposer.spec.ts
@@ -0,0 +1,46 @@
+import { URLDecomposer } from "./URLDecomposer";
+import Assert = require("assert");
+
+describe("utils/URLDecomposer", function () {
+  describe("test fromUrl", function () {
+    it("should return domain from https url", function () {
+      const d = URLDecomposer.fromUrl("https://www.example.com/test/abc");
+      Assert.equal(d.domain, "www.example.com");
+      Assert.equal(d.path, "/test/abc");
+    });
+
+    it("should return domain from http url", function () {
+      const d = URLDecomposer.fromUrl("http://www.example.com/test/abc");
+      Assert.equal(d.domain, "www.example.com");
+      Assert.equal(d.path, "/test/abc");
+    });
+
+    it("should return domain when url contains port", function () {
+      const d = URLDecomposer.fromUrl("https://www.example.com:8080/test/abc");
+      Assert.equal(d.domain, "www.example.com");
+      Assert.equal(d.path, "/test/abc");
+    });
+
+    it("should return default path when no path provided", function () {
+      const d = URLDecomposer.fromUrl("https://www.example.com:8080");
+      Assert.equal(d.domain, "www.example.com");
+      Assert.equal(d.path, "/");
+    });
+
+    it("should return default path when provided", function () {
+      const d = URLDecomposer.fromUrl("https://www.example.com:8080/");
+      Assert.equal(d.domain, "www.example.com");
+      Assert.equal(d.path, "/");
+    });
+
+    it("should return undefined when does not match", function () {
+      const d = URLDecomposer.fromUrl("https:///abc/test");
+      Assert.equal(d, undefined);
+    });
+
+    it("should return undefined when does not match", function () {
+      const d = URLDecomposer.fromUrl("https:///abc/test");
+      Assert.equal(d, undefined);
+    });
+  });
+});
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/utils/URLDecomposer.ts b/themes/triangles/server/src/lib/utils/URLDecomposer.ts
new file mode 100644
index 00000000..9bdf2e9d
--- /dev/null
+++ b/themes/triangles/server/src/lib/utils/URLDecomposer.ts
@@ -0,0 +1,15 @@
+export class URLDecomposer {
+  static fromUrl(url: string): {domain: string, path: string} {
+    if (!url) return;
+    const match = url.match(/https?:\/\/([a-z0-9_.-]+)(:[0-9]+)?(.*)/);
+
+    if (!match) return;
+
+    if (match[1] && !match[3]) {
+      return {domain: match[1], path: "/"};
+    } else if (match[1] && match[3]) {
+      return {domain: match[1], path: match[3]};
+    }
+    return;
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/web_server/Configurator.ts b/themes/triangles/server/src/lib/web_server/Configurator.ts
new file mode 100644
index 00000000..6e404874
--- /dev/null
+++ b/themes/triangles/server/src/lib/web_server/Configurator.ts
@@ -0,0 +1,47 @@
+import { Configuration } from "../configuration/schema/Configuration";
+import { GlobalDependencies } from "../../../types/Dependencies";
+import { SessionConfigurationBuilder } from
+  "../configuration/SessionConfigurationBuilder";
+import Path = require("path");
+import Express = require("express");
+import * as BodyParser from "body-parser";
+import { RestApi } from "./RestApi";
+import { WithHeadersLogged } from "./middlewares/WithHeadersLogged";
+import { ServerVariables } from "../ServerVariables";
+import Helmet = require("helmet");
+
+const addRequestId = require("express-request-id")();
+
+// Constants
+const TRUST_PROXY = "trust proxy";
+const X_POWERED_BY = "x-powered-by";
+const VIEWS = "views";
+const VIEW_ENGINE = "view engine";
+const PUG = "pug";
+
+export class Configurator {
+  static configure(config: Configuration,
+    app: Express.Application,
+    vars: ServerVariables,
+    deps: GlobalDependencies): void {
+    const viewsDirectory = Path.resolve(__dirname, "../../views");
+    const publicHtmlDirectory = Path.resolve(__dirname, "../../public_html");
+
+    const expressSessionOptions = SessionConfigurationBuilder.build(config, deps);
+
+    app.use(Express.static(publicHtmlDirectory));
+    app.use(BodyParser.urlencoded({ extended: false }));
+    app.use(BodyParser.json());
+    app.use(deps.session(expressSessionOptions));
+    app.use(addRequestId);
+    app.use(WithHeadersLogged.middleware(vars.logger));
+    app.disable(X_POWERED_BY);
+    app.enable(TRUST_PROXY);
+    app.use(Helmet());
+
+    app.set(VIEWS, viewsDirectory);
+    app.set(VIEW_ENGINE, PUG);
+
+    RestApi.setup(app, vars);
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/web_server/RestApi.ts b/themes/triangles/server/src/lib/web_server/RestApi.ts
new file mode 100644
index 00000000..9144a15b
--- /dev/null
+++ b/themes/triangles/server/src/lib/web_server/RestApi.ts
@@ -0,0 +1,125 @@
+import Express = require("express");
+
+import FirstFactorGet = require("../routes/firstfactor/get");
+import SecondFactorGet = require("../routes/secondfactor/get");
+
+import FirstFactorPost = require("../routes/firstfactor/post");
+import LogoutGet = require("../routes/logout/get");
+import VerifyGet = require("../routes/verify/get");
+import TOTPSignGet = require("../routes/secondfactor/totp/sign/post");
+
+import IdentityCheckMiddleware = require("../IdentityCheckMiddleware");
+
+import TOTPRegistrationIdentityHandler from "../routes/secondfactor/totp/identity/RegistrationHandler";
+import U2FRegistrationIdentityHandler from "../routes/secondfactor/u2f/identity/RegistrationHandler";
+import ResetPasswordIdentityHandler from "../routes/password-reset/identity/PasswordResetHandler";
+
+import U2FSignPost = require("../routes/secondfactor/u2f/sign/post");
+import U2FSignRequestGet = require("../routes/secondfactor/u2f/sign_request/get");
+
+import U2FRegisterPost = require("../routes/secondfactor/u2f/register/post");
+import U2FRegisterRequestGet = require("../routes/secondfactor/u2f/register_request/get");
+
+import ResetPasswordFormPost = require("../routes/password-reset/form/post");
+import ResetPasswordRequestPost = require("../routes/password-reset/request/get");
+
+import Error401Get = require("../routes/error/401/get");
+import Error403Get = require("../routes/error/403/get");
+import Error404Get = require("../routes/error/404/get");
+
+import LoggedIn = require("../routes/loggedin/get");
+
+import { ServerVariables } from "../ServerVariables";
+import Endpoints = require("../../../../shared/api");
+import { RequireValidatedFirstFactor } from "./middlewares/RequireValidatedFirstFactor";
+
+function setupTotp(app: Express.Application, vars: ServerVariables) {
+  app.post(Endpoints.SECOND_FACTOR_TOTP_POST,
+    RequireValidatedFirstFactor.middleware(vars.logger),
+    TOTPSignGet.default(vars));
+
+  app.get(Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger));
+
+  app.get(Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger));
+
+  IdentityCheckMiddleware.register(app,
+    Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
+    Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET,
+    new TOTPRegistrationIdentityHandler(vars.logger,
+      vars.userDataStore, vars.totpHandler, vars.config.totp),
+    vars);
+}
+
+function setupU2f(app: Express.Application, vars: ServerVariables) {
+  app.get(Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger),
+    U2FSignRequestGet.default(vars));
+
+  app.post(Endpoints.SECOND_FACTOR_U2F_SIGN_POST,
+    RequireValidatedFirstFactor.middleware(vars.logger),
+    U2FSignPost.default(vars));
+
+  app.get(Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger),
+    U2FRegisterRequestGet.default(vars));
+
+  app.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST,
+    RequireValidatedFirstFactor.middleware(vars.logger),
+    U2FRegisterPost.default(vars));
+
+  app.get(Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger));
+
+  app.get(Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET,
+    RequireValidatedFirstFactor.middleware(vars.logger));
+
+  IdentityCheckMiddleware.register(app,
+    Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET,
+    Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET,
+    new U2FRegistrationIdentityHandler(vars.logger), vars);
+}
+
+function setupResetPassword(app: Express.Application, vars: ServerVariables) {
+  IdentityCheckMiddleware.register(app,
+    Endpoints.RESET_PASSWORD_IDENTITY_START_GET,
+    Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET,
+    new ResetPasswordIdentityHandler(vars.logger, vars.usersDatabase),
+    vars);
+
+  app.get(Endpoints.RESET_PASSWORD_REQUEST_GET,
+    ResetPasswordRequestPost.default);
+  app.post(Endpoints.RESET_PASSWORD_FORM_POST,
+    ResetPasswordFormPost.default(vars));
+}
+
+function setupErrors(app: Express.Application, vars: ServerVariables) {
+  app.get(Endpoints.ERROR_401_GET, Error401Get.default(vars));
+  app.get(Endpoints.ERROR_403_GET, Error403Get.default(vars));
+  app.get(Endpoints.ERROR_404_GET, Error404Get.default);
+}
+
+export class RestApi {
+  static setup(app: Express.Application, vars: ServerVariables): void {
+    app.get(Endpoints.FIRST_FACTOR_GET, FirstFactorGet.default(vars));
+
+    app.get(Endpoints.SECOND_FACTOR_GET,
+      RequireValidatedFirstFactor.middleware(vars.logger),
+      SecondFactorGet.default(vars));
+
+    app.get(Endpoints.LOGOUT_GET, LogoutGet.default(vars));
+
+    app.get(Endpoints.VERIFY_GET, VerifyGet.default(vars));
+    app.post(Endpoints.FIRST_FACTOR_POST, FirstFactorPost.default(vars));
+
+    setupTotp(app, vars);
+    setupU2f(app, vars);
+    setupResetPassword(app, vars);
+    setupErrors(app, vars);
+
+    app.get(Endpoints.LOGGED_IN,
+      RequireValidatedFirstFactor.middleware(vars.logger),
+      LoggedIn.default(vars));
+  }
+}
diff --git a/themes/triangles/server/src/lib/web_server/middlewares/RequireValidatedFirstFactor.ts b/themes/triangles/server/src/lib/web_server/middlewares/RequireValidatedFirstFactor.ts
new file mode 100644
index 00000000..ecfd7576
--- /dev/null
+++ b/themes/triangles/server/src/lib/web_server/middlewares/RequireValidatedFirstFactor.ts
@@ -0,0 +1,27 @@
+import Express = require("express");
+import BluebirdPromise = require("bluebird");
+import ErrorReplies = require("../../ErrorReplies");
+import { IRequestLogger } from "../../logging/IRequestLogger";
+import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
+import Exceptions = require("../../Exceptions");
+import { Level } from "../../authentication/Level";
+
+export class RequireValidatedFirstFactor {
+  static middleware(logger: IRequestLogger) {
+    return function (req: Express.Request, res: Express.Response,
+      next: Express.NextFunction): BluebirdPromise<void> {
+
+      return new BluebirdPromise<void>(function (resolve, reject) {
+        const authSession = AuthenticationSessionHandler.get(req, logger);
+        if (!authSession.userid || authSession.authentication_level < Level.ONE_FACTOR)
+          return reject(
+            new Exceptions.FirstFactorValidationError(
+              "First factor has not been validated yet."));
+
+        next();
+        resolve();
+      })
+        .catch(ErrorReplies.replyWithError401(req, res, logger));
+    };
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/lib/web_server/middlewares/WithHeadersLogged.ts b/themes/triangles/server/src/lib/web_server/middlewares/WithHeadersLogged.ts
new file mode 100644
index 00000000..139db114
--- /dev/null
+++ b/themes/triangles/server/src/lib/web_server/middlewares/WithHeadersLogged.ts
@@ -0,0 +1,12 @@
+import Express = require("express");
+import { IRequestLogger } from "../../logging/IRequestLogger";
+
+export class WithHeadersLogged {
+  static middleware(logger: IRequestLogger) {
+    return function (req: Express.Request, res: Express.Response,
+      next: Express.NextFunction): void {
+      logger.debug(req, "Headers = %s", JSON.stringify(req.headers));
+      next();
+    };
+  }
+}
\ No newline at end of file
diff --git a/themes/triangles/server/src/resources/email-template.ejs b/themes/triangles/server/src/resources/email-template.ejs
new file mode 100644
index 00000000..f59c2f94
--- /dev/null
+++ b/themes/triangles/server/src/resources/email-template.ejs
@@ -0,0 +1,254 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+   <head>
+      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+      <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+      <title>Simples-Minimalistic Responsive Template</title>
+      
+      <style type="text/css">
+         /* Client-specific Styles */
+         #outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
+         body{background: rgb(0, 0, 0);width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
+         /* Prevent Webkit and Windows Mobile platforms from changing default font sizes, while not breaking desktop design. */
+         .ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
+         .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;} /* Force Hotmail to display normal line spacing.*/
+         #backgroundTable {margin:0; padding:0; width:100% !important; line-height: 100% !important;}
+         img {outline:none; text-decoration:none;border:none; -ms-interpolation-mode: bicubic;}
+         a img {border:none;}
+         .image_fix {display:block;}
+         p {margin: 0px 0px !important;}
+         table td {border-collapse: collapse;}
+         table { border-collapse:collapse; mso-table-lspace:0pt; mso-table-rspace:0pt; }
+         a {color: #ffffff;text-decoration: none;text-decoration:none!important;}
+          h1 {color: #ffffff; line-height: 30px; }
+         .button {padding: 15px 30px; border-radius: 10px; background: rgb(3, 183, 3); text-decoration:none; }
+
+         /*STYLES*/
+         table[class=full] { width: 100%; clear: both; }
+         /*IPAD STYLES*/
+         @media only screen and (max-width: 640px) {
+         a[href^="tel"], a[href^="sms"] {
+         text-decoration: none;
+         color: #ffffff; /* or whatever your want */
+         pointer-events: none;
+         cursor: default;
+         }
+         .mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
+         text-decoration: default;
+         color: #000000 !important;
+         pointer-events: auto;
+         cursor: default;
+         }
+         table[class=devicewidth] {width: 440px!important;text-align:center!important;}
+         table[class=devicewidthinner] {width: 420px!important;text-align:center!important;}
+         img[class=banner] {width: 440px!important;height:220px!important;}
+         img[class=colimg2] {width: 440px!important;height:220px!important;}
+         
+         }
+         /*IPHONE STYLES*/
+         @media only screen and (max-width: 480px) {
+         a[href^="tel"], a[href^="sms"] {
+         text-decoration: none;
+         color: #000000; /* or whatever your want */
+         pointer-events: none;
+         cursor: default;
+         }
+         .mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
+         text-decoration: default;
+         color: #000000 !important; 
+         pointer-events: auto;
+         cursor: default;
+         }
+         table[class=devicewidth] {width: 280px!important;text-align:center!important;}
+         table[class=devicewidthinner] {width: 260px!important;text-align:center!important;}
+         img[class=banner] {width: 280px!important;height:140px!important;}
+         img[class=colimg2] {width: 280px!important;height:140px!important;}
+         td[class=mobile-hide]{display:none!important;}
+         td[class="padding-bottom25"]{padding-bottom:25px!important;}
+        
+         }
+      </style>
+   </head>
+   <body>
+<!-- Start of header -->
+<table width="100%" bgcolor="#000000" cellpadding="0" cellspacing="0" border="0" id="backgroundTable" st-sortable="header">
+   <tbody>
+      <tr>
+         <td>
+            <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+               <tbody>
+                  <tr>
+                     <td width="100%">
+                        <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+                           <tbody>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td height="20" style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">&nbsp;</td>
+                              </tr>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td>
+                                    <!-- logo -->
+                                    <table width="140" align="center" border="0" cellpadding="0" cellspacing="0" class="devicewidth">
+                                       <tbody>
+                                          <tr>
+                                             <td width="300" height="50" align="center">
+                                              	<h1><%= title %></h1>
+                                             </td>
+                                          </tr>
+                                       </tbody>
+                                    </table>
+                                    <!-- end of logo -->
+                                 </td>
+                              </tr>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td height="20" style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">&nbsp;</td>
+                              </tr>
+                              <!-- Spacing -->
+                           </tbody>
+                        </table>
+                     </td>
+                  </tr>
+               </tbody>
+            </table>
+         </td>
+      </tr>
+   </tbody>
+</table>
+<!-- End of Header -->
+<!-- Start of seperator -->
+<table width="100%" bgcolor="#000000" cellpadding="0" cellspacing="0" border="0" id="backgroundTable" st-sortable="seperator">
+   <tbody>
+      <tr>
+         <td>
+            <table width="600" align="center" cellspacing="0" cellpadding="0" border="0" class="devicewidth">
+               <tbody>
+                  <tr>
+                     <td align="center" height="20" style="font-size:1px; line-height:1px;">&nbsp;</td>
+                  </tr>
+               </tbody>
+            </table>
+         </td>
+      </tr>
+   </tbody>
+</table>
+<!-- End of seperator -->   
+<!-- Start Full Text -->
+<table width="100%" bgcolor="#000000" cellpadding="0" cellspacing="0" border="0" id="backgroundTable" st-sortable="full-text">
+   <tbody>
+      <tr>
+         <td>
+            <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+               <tbody>
+                  <tr>
+                     <td width="100%">
+                        <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+                           <tbody>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td height="20" style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">&nbsp;</td>
+                              </tr>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td>
+                                    <table width="560" align="center" cellpadding="0" cellspacing="0" border="0" class="devicewidthinner">
+                                       <tbody>
+                                          <!-- Title -->
+                                          <tr>
+                                             <td style="font-family: Helvetica, arial, sans-serif; font-size: 16px; color: #ffffff; text-align:center; line-height: 30px;" st-title="fulltext-content">
+                                                This email has been sent to you in order to validate your identity. Please ignore it if you do not know why you received it.
+                                             </td>
+                                          </tr>
+                                          <!-- End of Title -->
+                                          <!-- spacing -->
+                                          <tr>
+                                             <td width="100%" height="20" style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">&nbsp;</td>
+                                          </tr>
+                                          <!-- End of spacing -->
+                                          <!-- content -->
+                                          <tr>
+                                             <td style="font-family: Helvetica, arial, sans-serif; font-size: 16px; color: #666666; text-align:center; line-height: 30px;" st-content="fulltext-content">
+                                                 <a href="<%= url %>" class="button"><%= button_title %></a>
+                                             </td>
+                                          </tr>
+                                          <!-- End of content -->
+                                       </tbody>
+                                    </table>
+                                 </td>
+                              </tr>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td height="20" style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">&nbsp;</td>
+                              </tr>
+                              <!-- Spacing -->
+                           </tbody>
+                        </table>
+                     </td>
+                  </tr>
+               </tbody>
+            </table>
+         </td>
+      </tr>
+   </tbody>
+</table>
+<!-- end of full text -->
+<!-- Start of seperator -->
+<table width="100%" bgcolor="#000000" cellpadding="0" cellspacing="0" border="0" id="backgroundTable" st-sortable="seperator">
+   <tbody>
+      <tr>
+         <td>
+            <table width="600" align="center" cellspacing="0" cellpadding="0" border="0" class="devicewidth">
+               <tbody>
+                  <tr>
+                     <td align="center" height="30" style="font-size:1px; line-height:1px;">&nbsp;</td>
+                  </tr>
+                  <tr>
+                     <td width="550" align="center" height="1" bgcolor="#d1d1d1" style="font-size:1px; line-height:1px;">&nbsp;</td>
+                  </tr>
+                  <tr>
+                     <td align="center" height="30" style="font-size:1px; line-height:1px;">&nbsp;</td>
+                  </tr>
+               </tbody>
+            </table>
+         </td>
+      </tr>
+   </tbody>
+</table>
+<!-- End of seperator -->  
+<!-- Start of Postfooter -->
+<table width="100%" bgcolor="#000000" cellpadding="0" cellspacing="0" border="0" id="backgroundTable" st-sortable="postfooter" >
+   <tbody>
+      <tr>
+         <td>
+            <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+               <tbody>
+                  <tr>
+                     <td width="100%">
+                        <table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
+                           <tbody>
+                              <tr>
+                                 <td align="center" valign="middle" style="font-family: Helvetica, arial, sans-serif; font-size: 14px;color: #ffffff" st-content="postfooter">
+									Please ignore this email if you did not initiate the process.
+                                 </td>
+                              </tr>
+                              <!-- Spacing -->
+                              <tr>
+                                 <td width="100%" height="20"></td>
+                              </tr>
+                              <!-- Spacing -->
+                           </tbody>
+                        </table>
+                     </td>
+                  </tr>
+               </tbody>
+            </table>
+         </td>
+      </tr>
+   </tbody>
+</table>
+<!-- End of postfooter -->
+   
+   </body>
+   </html>
+
diff --git a/themes/triangles/server/src/views/already-logged-in.pug b/themes/triangles/server/src/views/already-logged-in.pug
new file mode 100644
index 00000000..137bbea3
--- /dev/null
+++ b/themes/triangles/server/src/views/already-logged-in.pug
@@ -0,0 +1,14 @@
+extends layout/layout.pug 
+
+block form-header
+  h1 Sign in
+
+block content
+  img(class="header-img" src="/img/success.png" alt="success")
+  if redirection_url
+    p You are already logged in as <b>#{ username }</b>.<br/><br/>
+      | If you are not redirected in few seconds, click <a href="#{ redirection_url }">here</a>.<br/><br/>
+      | Otherwise, click <a href="#{ logout_endpoint }">here</a> to log off.
+  else
+    p You are already logged in as <b>#{ username }</b>.<br/><br/>
+      | Click <a href="#{ logout_endpoint }">here</a> to log off.
diff --git a/themes/triangles/server/src/views/errors/.directory b/themes/triangles/server/src/views/errors/.directory
new file mode 100644
index 00000000..33f71bea
--- /dev/null
+++ b/themes/triangles/server/src/views/errors/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,59,57
+Version=3
+ViewMode=1
diff --git a/themes/triangles/server/src/views/errors/401.pug b/themes/triangles/server/src/views/errors/401.pug
new file mode 100644
index 00000000..b7a222ad
--- /dev/null
+++ b/themes/triangles/server/src/views/errors/401.pug
@@ -0,0 +1,16 @@
+extends ../layout/layout.pug
+
+block variables
+  - page_classname = "error-401";
+
+block form-header
+  h1 Error 401
+
+block content
+  img(class="header-img" src="/img/warning.png" alt="warning")
+  if redirection_url
+    p You are not authorized to access this resource.<br/><br/>
+      | Please click <a href=#{redirection_url}>here</a> if you are not 
+      | redirected in few seconds.
+  else
+    p You are not authorized to access this resource.
\ No newline at end of file
diff --git a/themes/triangles/server/src/views/errors/403.pug b/themes/triangles/server/src/views/errors/403.pug
new file mode 100644
index 00000000..f4b5ca8a
--- /dev/null
+++ b/themes/triangles/server/src/views/errors/403.pug
@@ -0,0 +1,16 @@
+extends ../layout/layout.pug
+
+block variables
+  - page_classname = "error-403";
+
+block form-header
+  h1 Error 403
+
+block content
+  img(class="header-img" src="/img/warning.png" alt="warning")
+  if redirection_url
+    p You don't have enough privileges to access this resource.<br/><br/>
+      | Please click <a href=#{redirection_url}>here</a> if you are not 
+      | redirected in few seconds.
+  else
+    p You don't have enough privileges to access this resource.
diff --git a/themes/triangles/server/src/views/errors/404.pug b/themes/triangles/server/src/views/errors/404.pug
new file mode 100644
index 00000000..06d6375f
--- /dev/null
+++ b/themes/triangles/server/src/views/errors/404.pug
@@ -0,0 +1,11 @@
+extends ../layout/layout.pug
+
+block variables
+  - page_classname = "error-404";
+
+block form-header
+  <h1>Error 404</h1>
+
+block content
+  img(class="header-img" src="/img/warning.png" alt="warning")
+  p Page not found.
diff --git a/themes/triangles/server/src/views/firstfactor.pug b/themes/triangles/server/src/views/firstfactor.pug
new file mode 100644
index 00000000..57447071
--- /dev/null
+++ b/themes/triangles/server/src/views/firstfactor.pug
@@ -0,0 +1,23 @@
+extends layout/layout.pug 
+
+block variables
+  - page_classname = "firstfactor";
+
+block form-header
+  h1 Sign in
+
+block content
+  div(class="notification")
+  img(class="header-img" src="/img/sharingan.png" alt="user profile")
+  p Enter your credentials to sign in
+  form(class="form-signin")
+    div(class="form-inputs")
+      input(type="text" class="form-control" id="username" placeholder="Username" required autofocus)
+      input(type="password" class="form-control" id="password" placeholder="Password" required)
+    button(id="signin" class="btn btn-lg btn-primary btn-block" type="submit") Sign in
+    div(class="keep-me-logged-in pull-left")
+      input(type="checkbox" id="keep_me_logged_in" name="keep_me_logged_in" value="true")
+      label(for="keep_me_logged_in") Keep me logged in
+    div(class="bottom-right-links pull-right")
+      a(href=reset_password_request_endpoint, class="link forgot-password") Forgot password?
+    span(class="clearfix")
diff --git a/themes/triangles/server/src/views/layout/layout.pug b/themes/triangles/server/src/views/layout/layout.pug
new file mode 100644
index 00000000..43247436
--- /dev/null
+++ b/themes/triangles/server/src/views/layout/layout.pug
@@ -0,0 +1,28 @@
+block variables
+
+doctype html
+html
+  head
+    title Authelia - 2FA
+    meta(name="viewport", content="width=device-width, initial-scale=1.0")
+    meta(name="robots", content="noindex, nofollow, nosnippet, noarchive")
+    meta(http-equiv="Content-Security-Policy", content="default-src 'self'; img-src 'self' data:;")
+    link(rel="icon", href="/img/icon.png" type="image/png" sizes="32x32")
+    link(rel="stylesheet", type="text/css", href="/css/authelia.css")
+    if redirection_url
+      meta(http-equiv="refresh" content="4;url=" + redirection_url) 
+  body
+    div(class="container")
+      div(class="row")
+        div(class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3")
+          div(class="account-wall " + page_classname)
+            div(class="row header")
+              block form-header
+            div(class="row body")
+              div(class="form col-xs-10 col-xs-offset-1 col-sm-8 col-sm-offset-2 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2")
+                block content
+            div(class="row footer poweredby-block")
+              div(class="poweredby col-xs-6 col-xs-offset-4 col-sm-6 col-sm-offset-4 col-md-6 col-md-offset-4")
+                | Powered by <a class="authelia-brand" href="https://github.com/clems4ever/authelia">Authelia</a>
+    block entrypoint
+    script(src="/js/authelia.js", type="text/javascript")
diff --git a/themes/triangles/server/src/views/need-identity-validation.pug b/themes/triangles/server/src/views/need-identity-validation.pug
new file mode 100644
index 00000000..4cfd6271
--- /dev/null
+++ b/themes/triangles/server/src/views/need-identity-validation.pug
@@ -0,0 +1,12 @@
+extends layout/layout.pug
+
+block variables
+  - page_classname = "identity-validation";
+
+block form-header
+  h1 Registration
+
+block content
+  img(class="header-img" src="/img/mail.png" alt="mail")
+  p A confirmation email has been sent to your mailbox. 
+    | Please open it and click on the link within 15 minutes to confirm the registration.
diff --git a/themes/triangles/server/src/views/password-reset-form.pug b/themes/triangles/server/src/views/password-reset-form.pug
new file mode 100644
index 00000000..fd931189
--- /dev/null
+++ b/themes/triangles/server/src/views/password-reset-form.pug
@@ -0,0 +1,18 @@
+extends layout/layout.pug 
+
+block variables
+  - page_classname = "password-reset-form";
+
+block form-header
+  h1 Reset password
+
+block content
+  div(class="notification")
+  img(class="header-img" src="/img/password_white.png" alt="password")
+  p Set your new password and confirm it.
+  form(class="form-signin")
+    div(class="form-inputs")
+      input(class="form-control" type="password" name="password1" id="password1" placeholder="New password" required="required")
+      input(class="form-control" type="password" name="password2" id="password2" placeholder="Password confirmation" required="required")
+    button(id="reset-password-button" class="btn btn-lg btn-primary btn-block" type="submit") Reset Password
+    span(class="clearfix")
diff --git a/themes/triangles/server/src/views/password-reset-request.pug b/themes/triangles/server/src/views/password-reset-request.pug
new file mode 100644
index 00000000..855b5998
--- /dev/null
+++ b/themes/triangles/server/src/views/password-reset-request.pug
@@ -0,0 +1,18 @@
+extends layout/layout.pug 
+
+block variables
+  - page_classname = "password-reset-request";
+
+block form-header
+  h1 Reset password
+
+block content
+  div(class="notification")
+  div
+    img(class="header-img" src="/img/password_white.png" alt="password")
+    p After giving your username, you will receive an email to change your password.
+  form(class="form-signin")
+    div(class="form-inputs")
+      input(type="text" class="form-control" name="username" id="username" placeholder="Your username" required="required")
+    button(id="reset-password-button" class="btn btn-lg btn-primary btn-block" type="submit") Reset Password
+    span(class="clearfix")
diff --git a/themes/triangles/server/src/views/secondfactor.pug b/themes/triangles/server/src/views/secondfactor.pug
new file mode 100644
index 00000000..87b57818
--- /dev/null
+++ b/themes/triangles/server/src/views/secondfactor.pug
@@ -0,0 +1,31 @@
+extends layout/layout.pug
+
+block variables
+  - page_classname = "secondfactor";
+
+block form-header
+  h1 Sign in
+
+block content
+  div
+    div(class="notification notification-totp")
+    h3 Hi <b>#{username}</b>
+  div(class="row")
+    div(class="u2f-token")
+      img(src="/img/pendrive.png", alt="security key")
+    p
+      | Please, touch your <a href="https://www.yubico.com/products/yubikey-hardware/fido-u2f-security-key/">security key</a><br/>
+      b Or<br/>
+      | Get a one-time password
+    form(class="form-signin totp")
+      div(class="form-inputs")
+        input(type="text" autocomplete="off" class="form-control" id="token" placeholder="Token" required autofocus)
+      button(class="btn btn-lg btn-primary btn-block totp-button" type="submit") Sign in
+      div(class="pull-right bottom-right-links")
+        div Need to register?
+        div
+          a(href=u2f_identity_start_endpoint, class="link register-u2f", data-toggle="tooltip", title="A security key is required to register.") Security key
+          |  | 
+          a(href=totp_identity_start_endpoint, class="link register-totp") Google Authenticator
+          span(class="clearfix")
+  script(src="/js/u2f-api.js", type="text/javascript")
diff --git a/themes/triangles/server/src/views/totp-register.pug b/themes/triangles/server/src/views/totp-register.pug
new file mode 100644
index 00000000..1b4d9835
--- /dev/null
+++ b/themes/triangles/server/src/views/totp-register.pug
@@ -0,0 +1,25 @@
+extends layout/layout.pug 
+
+block variables
+  - page_classname = "totp-register";
+
+block form-header
+  h1 One-time passwords
+
+block content
+  p Open Google Authenticator and add this entry
+  p(id="secret") #{ base32_secret }
+  p or scan this barcode
+  div(id="qrcode") #{ otpauth_url }
+  p
+    a(href=login_endpoint, id="login-button") Login
+  div(class="need-google-authenticator")
+    | Need Google Authenticator?
+    div(class="store-badges")
+      a(href='https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1', target="_blank")
+        img(alt='Get it on Google Play', src='/img/stores/googleplay-badge.svg', class="store-badge")
+      a(href='https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8', target="_blank")
+        img(alt='Get it on Apple Store' src='/img/stores/applestore-badge.svg' class="store-badge")
+
+block entrypoint
+  script(src="/js/qrcode.min.js", type="text/javascript" )
diff --git a/themes/triangles/server/src/views/u2f-register.pug b/themes/triangles/server/src/views/u2f-register.pug
new file mode 100644
index 00000000..d52eba6c
--- /dev/null
+++ b/themes/triangles/server/src/views/u2f-register.pug
@@ -0,0 +1,12 @@
+extends layout/layout.pug 
+
+block variables
+  - page_classname = "u2f-register";
+
+block form-header
+  h1 Register your security key
+
+block content
+  p Touch the token to register your security key.
+  img(src="/img/pendrive.png" alt="pendrive")
+  script(src="/js/u2f-api.js", type="text/javascript")
\ No newline at end of file
diff --git a/themes/triangles/server/test/requests.ts b/themes/triangles/server/test/requests.ts
new file mode 100644
index 00000000..93fa0de4
--- /dev/null
+++ b/themes/triangles/server/test/requests.ts
@@ -0,0 +1,94 @@
+
+import BluebirdPromise = require("bluebird");
+import request = require("request");
+import assert = require("assert");
+import express = require("express");
+import nodemailer = require("nodemailer");
+import Endpoints = require("../../shared/api");
+
+declare module "request" {
+  export interface RequestAPI<TRequest extends Request,
+      TOptions extends CoreOptions,
+      TUriUrlOptions> {
+      getAsync(uri: string, options?: RequiredUriUrl): BluebirdPromise<RequestResponse>;
+      getAsync(uri: string): BluebirdPromise<RequestResponse>;
+      getAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise<RequestResponse>;
+
+      postAsync(uri: string, options?: CoreOptions): BluebirdPromise<RequestResponse>;
+      postAsync(uri: string): BluebirdPromise<RequestResponse>;
+      postAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise<RequestResponse>;
+  }
+}
+
+const requestAsync: typeof request = BluebirdPromise.promisifyAll(request) as typeof request;
+
+export = function (port: number) {
+  const PORT = port;
+  const BASE_URL = "http://localhost:" + PORT;
+
+  function execute_totp(jar: request.CookieJar, token: string) {
+    return requestAsync.postAsync({
+      url: BASE_URL + Endpoints.SECOND_FACTOR_TOTP_POST,
+      jar: jar,
+      form: {
+        token: token
+      }
+    });
+  }
+
+  function execute_u2f_authentication(jar: request.CookieJar) {
+    return requestAsync.getAsync({
+      url: BASE_URL + Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET,
+      jar: jar
+    })
+      .then(function (res: request.RequestResponse) {
+        assert.equal(res.statusCode, 200);
+        return requestAsync.postAsync({
+          url: BASE_URL + Endpoints.SECOND_FACTOR_U2F_SIGN_POST,
+          jar: jar,
+          form: {
+          }
+        });
+      });
+  }
+
+  function execute_verification(jar: request.CookieJar) {
+    return requestAsync.getAsync({ url: BASE_URL + Endpoints.VERIFY_GET, jar: jar });
+  }
+
+  function execute_login(jar: request.CookieJar) {
+    return requestAsync.getAsync({ url: BASE_URL + Endpoints.FIRST_FACTOR_GET, jar: jar });
+  }
+
+  function execute_first_factor(jar: request.CookieJar) {
+    return requestAsync.postAsync({
+      url: BASE_URL + Endpoints.FIRST_FACTOR_POST,
+      jar: jar,
+      form: {
+        username: "test_ok",
+        password: "password"
+      }
+    });
+  }
+
+  function execute_failing_first_factor(jar: request.CookieJar) {
+    return requestAsync.postAsync({
+      url: BASE_URL + Endpoints.FIRST_FACTOR_POST,
+      jar: jar,
+      form: {
+        username: "test_nok",
+        password: "password"
+      }
+    });
+  }
+
+  return {
+    login: execute_login,
+    verify: execute_verification,
+    u2f_authentication: execute_u2f_authentication,
+    first_factor: execute_first_factor,
+    failing_first_factor: execute_failing_first_factor,
+    totp: execute_totp,
+  };
+};
+
diff --git a/themes/triangles/server/tsconfig.json b/themes/triangles/server/tsconfig.json
new file mode 100644
index 00000000..ebe98c5e
--- /dev/null
+++ b/themes/triangles/server/tsconfig.json
@@ -0,0 +1,21 @@
+{
+    "compilerOptions": {
+        "module": "commonjs",
+        "target": "es6",
+        "moduleResolution": "node",
+        "noImplicitAny": true,
+        "sourceMap": true,
+        "removeComments": true,
+        "outDir": "../dist",
+        "baseUrl": ".",
+        "paths": {
+            "*": [
+                "./types/*",
+                "../shared/types/*"
+            ]
+        }
+    },
+    "exclude": [
+        "src/**/*.spec.ts"
+    ]
+}
diff --git a/themes/triangles/server/tslint.json b/themes/triangles/server/tslint.json
new file mode 100644
index 00000000..c2c1b750
--- /dev/null
+++ b/themes/triangles/server/tslint.json
@@ -0,0 +1,60 @@
+{
+    "rules": {
+        "class-name": true,
+        "comment-format": [
+            true,
+            "check-space"
+        ],
+        "indent": [
+            true,
+            "spaces"
+        ],
+        "one-line": [
+            true,
+            "check-open-brace",
+            "check-whitespace"
+        ],
+        "no-var-keyword": true,
+        "quotemark": [
+            true,
+            "double",
+            "avoid-escape"
+        ],
+        "semicolon": [
+            true,
+            "always",
+            "ignore-bound-class-methods"
+        ],
+        "whitespace": [
+            true,
+            "check-branch",
+            "check-decl",
+            "check-operator",
+            "check-module",
+            "check-separator",
+            "check-type"
+        ],
+        "typedef-whitespace": [
+            true,
+            {
+                "call-signature": "nospace",
+                "index-signature": "nospace",
+                "parameter": "nospace",
+                "property-declaration": "nospace",
+                "variable-declaration": "nospace"
+            },
+            {
+                "call-signature": "onespace",
+                "index-signature": "onespace",
+                "parameter": "onespace",
+                "property-declaration": "onespace",
+                "variable-declaration": "onespace"
+            }
+        ],
+        "no-internal-module": true,
+        "no-trailing-whitespace": true,
+        "no-null-keyword": true,
+        "prefer-const": true,
+        "jsdoc-format": true
+    }
+}
diff --git a/themes/triangles/server/types/.directory b/themes/triangles/server/types/.directory
new file mode 100644
index 00000000..1e65000e
--- /dev/null
+++ b/themes/triangles/server/types/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2018,12,17,20,58,27
+Version=3
+ViewMode=1
diff --git a/themes/triangles/server/types/AuthenticationSession.ts b/themes/triangles/server/types/AuthenticationSession.ts
new file mode 100644
index 00000000..bbed0e71
--- /dev/null
+++ b/themes/triangles/server/types/AuthenticationSession.ts
@@ -0,0 +1,18 @@
+import U2f = require("u2f");
+import { Level } from "../src/lib/authentication/Level";
+
+export interface AuthenticationSession {
+  userid: string;
+  authentication_level: Level;
+  keep_me_logged_in: boolean;
+  last_activity_datetime: number;
+  identity_check?: {
+    challenge: string;
+    userid: string;
+  };
+  register_request?: U2f.Request;
+  sign_request?: U2f.Request;
+  email: string;
+  groups: string[];
+  redirect?: string;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/types/Dependencies.ts b/themes/triangles/server/types/Dependencies.ts
new file mode 100644
index 00000000..f20404db
--- /dev/null
+++ b/themes/triangles/server/types/Dependencies.ts
@@ -0,0 +1,29 @@
+import winston = require("winston");
+import speakeasy = require("speakeasy");
+import nodemailer = require("nodemailer");
+import session = require("express-session");
+import nedb = require("nedb");
+import ldapjs = require("ldapjs");
+import u2f = require("u2f");
+import RedisSession = require("connect-redis");
+import Redis = require("redis");
+
+export type Speakeasy = typeof speakeasy;
+export type Winston = typeof winston;
+export type Session = typeof session;
+export type Nedb = typeof nedb;
+export type Ldapjs = typeof ldapjs;
+export type U2f = typeof u2f;
+export type ConnectRedis = typeof RedisSession;
+export type Redis = typeof Redis;
+
+export interface GlobalDependencies {
+    u2f: U2f;
+    ldapjs: Ldapjs;
+    session: Session;
+    Redis: Redis;
+    ConnectRedis: ConnectRedis;
+    winston: Winston;
+    speakeasy: Speakeasy;
+    nedb: Nedb;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/types/Identity.ts b/themes/triangles/server/types/Identity.ts
new file mode 100644
index 00000000..e985984e
--- /dev/null
+++ b/themes/triangles/server/types/Identity.ts
@@ -0,0 +1,6 @@
+
+
+export interface Identity {
+    userid: string;
+    email: string;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/types/TOTPSecret.ts b/themes/triangles/server/types/TOTPSecret.ts
new file mode 100644
index 00000000..d6775f2f
--- /dev/null
+++ b/themes/triangles/server/types/TOTPSecret.ts
@@ -0,0 +1,11 @@
+
+export interface TOTPSecret {
+    ascii: string;
+    hex: string;
+    base32: string;
+    qr_code_ascii: string;
+    qr_code_hex: string;
+    qr_code_base32: string;
+    google_auth_qr: string;
+    otpauth_url: string;
+  }
\ No newline at end of file
diff --git a/themes/triangles/server/types/U2FRegistration.ts b/themes/triangles/server/types/U2FRegistration.ts
new file mode 100644
index 00000000..b6080af0
--- /dev/null
+++ b/themes/triangles/server/types/U2FRegistration.ts
@@ -0,0 +1,5 @@
+
+export interface U2FRegistration {
+  keyHandle: string;
+  publicKey: string;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/types/dovehash.d.ts b/themes/triangles/server/types/dovehash.d.ts
new file mode 100644
index 00000000..c354609c
--- /dev/null
+++ b/themes/triangles/server/types/dovehash.d.ts
@@ -0,0 +1,4 @@
+
+declare module "dovehash" {
+    function encode(algo: string, text: string): string;
+}
\ No newline at end of file
diff --git a/themes/triangles/server/types/speakeasy.d.ts b/themes/triangles/server/types/speakeasy.d.ts
new file mode 100644
index 00000000..6ea06948
--- /dev/null
+++ b/themes/triangles/server/types/speakeasy.d.ts
@@ -0,0 +1,96 @@
+declare module "speakeasy" {
+  export = speakeasy
+
+  interface SharedOptions {
+    encoding?: string
+    algorithm?: string
+  }
+
+  interface DigestOptions extends SharedOptions {
+    secret: string
+    counter: number
+  }
+
+  interface HOTPOptions extends SharedOptions {
+    secret: string
+    counter: number
+    digest?: Buffer
+    digits?: number
+  }
+
+  interface HOTPVerifyOptions extends SharedOptions {
+    secret: string
+    token: string
+    counter: number
+    digits?: number
+    window?: number
+  }
+
+  interface TOTPOptions extends SharedOptions {
+    secret: string
+    time?: number
+    step?: number
+    epoch?: number
+    counter?: number
+    digits?: number
+  }
+
+  interface TOTPVerifyOptions extends SharedOptions {
+    secret: string
+    token: string
+    time?: number
+    step?: number
+    epoch?: number
+    counter?: number
+    digits?: number
+    window?: number
+  }
+
+  interface GenerateSecretOptions {
+    length?: number
+    symbols?: boolean
+    otpauth_url?: boolean
+    name?: string
+    issuer?: string
+  }
+
+  interface GeneratedSecret {
+    ascii: string
+    hex: string
+    base32: string
+    qr_code_ascii: string
+    qr_code_hex: string
+    qr_code_base32: string
+    google_auth_qr: string
+    otpauth_url: string
+  }
+
+  interface OTPAuthURLOptions extends SharedOptions {
+    secret: string
+    label: string
+    type?: string
+    counter?: number
+    issuer?: string
+    digits?: number
+    period?: number
+  }
+
+  interface Speakeasy {
+    digest: (options: DigestOptions) => Buffer
+    hotp: {
+      (options: HOTPOptions): string,
+      verifyDelta: (options: HOTPVerifyOptions) => boolean,
+      verify: (options: HOTPVerifyOptions) => boolean,
+    }
+    totp: {
+      (options: TOTPOptions): string
+      verifyDelta: (options: TOTPVerifyOptions) => boolean,
+      verify: (options: TOTPVerifyOptions) => boolean,
+    }
+    generateSecret: (options?: GenerateSecretOptions) => GeneratedSecret
+    generateSecretASCII: (length?: number, symbols?: boolean) => string
+    otpauthURL: (options: OTPAuthURLOptions) => string
+  }
+
+  const speakeasy: Speakeasy
+}
\ No newline at end of file