Add support for users database on disk. (#262)
In order to simplify the deployment of Authelia for testing, LDAP is now optional made optional thanks to users database stored in a file. One can update the file manually even while Authelia is running. With this feature the minimal configuration requires only two components: Authelia and nginx. The users database is obviously made for development environments only as it prevents Authelia to be scaled to more than one instance. Note: Configuration has been updated. Key `ldap` has been nested in `authentication_backend`.pull/263/head
parent
1f5a18d12a
commit
9dab40c2ce
|
@ -34,3 +34,5 @@ dist/
|
||||||
example/ldap/private.ldif
|
example/ldap/private.ldif
|
||||||
|
|
||||||
Configuration.schema.json
|
Configuration.schema.json
|
||||||
|
|
||||||
|
users_database.test.yml
|
||||||
|
|
|
@ -2,17 +2,10 @@
|
||||||
# Authelia minimal configuration #
|
# Authelia minimal configuration #
|
||||||
###############################################################
|
###############################################################
|
||||||
|
|
||||||
ldap:
|
authentication_backend:
|
||||||
url: ldap://openldap
|
file:
|
||||||
base_dn: dc=example,dc=com
|
# The path to the database file. The file is at the root of the repo.
|
||||||
|
path: /etc/authelia/users_database.yml
|
||||||
additional_users_dn: ou=users
|
|
||||||
additional_groups_dn: ou=groups
|
|
||||||
|
|
||||||
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
|
||||||
|
|
||||||
user: cn=admin,dc=example,dc=com
|
|
||||||
password: password
|
|
||||||
|
|
||||||
session:
|
session:
|
||||||
# The secret to encrypt the session cookies with.
|
# The secret to encrypt the session cookies with.
|
||||||
|
|
|
@ -29,43 +29,61 @@ default_redirection_url: https://home.example.com:8080/
|
||||||
totp:
|
totp:
|
||||||
issuer: authelia.com
|
issuer: authelia.com
|
||||||
|
|
||||||
|
# The authentication backend to use for verifying user passwords
|
||||||
# LDAP configuration
|
# and retrieve information such as email address and groups
|
||||||
|
# users belong to.
|
||||||
#
|
#
|
||||||
# Example: for user john, the DN will be cn=john,ou=users,dc=example,dc=com
|
# There are two supported backends: `ldap` and `file`.
|
||||||
ldap:
|
authentication_backend:
|
||||||
# The url of the ldap server
|
# LDAP backend configuration.
|
||||||
url: ldap://openldap
|
#
|
||||||
|
# This backend allows Authelia to be scaled to more
|
||||||
|
# than one instance and therefore is recommended for
|
||||||
|
# production.
|
||||||
|
ldap:
|
||||||
|
# The url of the ldap server
|
||||||
|
url: ldap://openldap
|
||||||
|
|
||||||
# The base dn for every entries
|
# The base dn for every entries
|
||||||
base_dn: dc=example,dc=com
|
base_dn: dc=example,dc=com
|
||||||
|
|
||||||
# An additional dn to define the scope to all users
|
# An additional dn to define the scope to all users
|
||||||
additional_users_dn: ou=users
|
additional_users_dn: ou=users
|
||||||
|
|
||||||
# The users filter used to find the user DN
|
# The users filter used to find the user DN
|
||||||
# {0} is a matcher replaced by username.
|
# {0} is a matcher replaced by username.
|
||||||
# 'cn={0}' by default.
|
# 'cn={0}' by default.
|
||||||
users_filter: cn={0}
|
users_filter: cn={0}
|
||||||
|
|
||||||
# An additional dn to define the scope of groups
|
# An additional dn to define the scope of groups
|
||||||
additional_groups_dn: ou=groups
|
additional_groups_dn: ou=groups
|
||||||
|
|
||||||
# The groups filter used for retrieving groups of a given user.
|
# The groups filter used for retrieving groups of a given user.
|
||||||
# {0} is a matcher replaced by username.
|
# {0} is a matcher replaced by username.
|
||||||
# {dn} is a matcher replaced by user DN.
|
# {dn} is a matcher replaced by user DN.
|
||||||
# 'member={dn}' by default.
|
# 'member={dn}' by default.
|
||||||
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
||||||
|
|
||||||
# The attribute holding the name of the group
|
# The attribute holding the name of the group
|
||||||
group_name_attribute: cn
|
group_name_attribute: cn
|
||||||
|
|
||||||
# The attribute holding the mail address of the user
|
# The attribute holding the mail address of the user
|
||||||
mail_attribute: mail
|
mail_attribute: mail
|
||||||
|
|
||||||
# The username and password of the admin user.
|
# The username and password of the admin user.
|
||||||
user: cn=admin,dc=example,dc=com
|
user: cn=admin,dc=example,dc=com
|
||||||
password: password
|
password: password
|
||||||
|
|
||||||
|
# File backend configuration.
|
||||||
|
#
|
||||||
|
# With this backend, the users database is stored in a file
|
||||||
|
# which is updated when users reset their passwords.
|
||||||
|
# Therefore, this backend is meant to be used in a dev environment
|
||||||
|
# and not in production since it prevents Authelia to be scaled to
|
||||||
|
# more than one instance.
|
||||||
|
#
|
||||||
|
## file:
|
||||||
|
## path: ./users_database.yml
|
||||||
|
|
||||||
|
|
||||||
# Authentication methods
|
# Authentication methods
|
||||||
|
@ -87,6 +105,7 @@ authentication_methods:
|
||||||
per_subdomain_methods:
|
per_subdomain_methods:
|
||||||
single_factor.example.com: single_factor
|
single_factor.example.com: single_factor
|
||||||
|
|
||||||
|
|
||||||
# Access Control
|
# Access Control
|
||||||
#
|
#
|
||||||
# Access control is a set of rules you can use to restrict user access to certain
|
# Access control is a set of rules you can use to restrict user access to certain
|
||||||
|
@ -217,8 +236,8 @@ regulation:
|
||||||
# You must use only an available configuration: local, mongo
|
# You must use only an available configuration: local, mongo
|
||||||
storage:
|
storage:
|
||||||
# The directory where the DB files will be saved
|
# The directory where the DB files will be saved
|
||||||
# local:
|
## local:
|
||||||
# path: /var/lib/authelia/store
|
## path: /var/lib/authelia/store
|
||||||
|
|
||||||
# Settings to connect to mongo server
|
# Settings to connect to mongo server
|
||||||
mongo:
|
mongo:
|
||||||
|
@ -232,16 +251,16 @@ storage:
|
||||||
# Use only an available configuration: filesystem, gmail
|
# Use only an available configuration: filesystem, gmail
|
||||||
notifier:
|
notifier:
|
||||||
# For testing purpose, notifications can be sent in a file
|
# For testing purpose, notifications can be sent in a file
|
||||||
# filesystem:
|
## filesystem:
|
||||||
# filename: /tmp/authelia/notification.txt
|
## filename: /tmp/authelia/notification.txt
|
||||||
|
|
||||||
# Use your email account to send the notifications. You can use an app password.
|
# Use your email account to send the notifications. You can use an app password.
|
||||||
# List of valid services can be found here: https://nodemailer.com/smtp/well-known/
|
# List of valid services can be found here: https://nodemailer.com/smtp/well-known/
|
||||||
# email:
|
## email:
|
||||||
# username: user@example.com
|
## username: user@example.com
|
||||||
# password: yourpassword
|
## password: yourpassword
|
||||||
# sender: admin@example.com
|
## sender: admin@example.com
|
||||||
# service: gmail
|
## service: gmail
|
||||||
|
|
||||||
# Use a SMTP server for sending notifications
|
# Use a SMTP server for sending notifications
|
||||||
smtp:
|
smtp:
|
||||||
|
|
|
@ -5,8 +5,9 @@ services:
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./config.minimal.yml:/etc/authelia/config.yml:ro
|
- ./config.minimal.yml:/etc/authelia/config.yml:ro
|
||||||
|
- ./users_database.test.yml:/etc/authelia/users_database.yml:rw
|
||||||
- /tmp/authelia:/tmp/authelia
|
- /tmp/authelia:/tmp/authelia
|
||||||
environment:
|
environment:
|
||||||
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||||
networks:
|
networks:
|
||||||
- example-network
|
- example-network
|
||||||
|
|
|
@ -42,11 +42,13 @@ describe("Server", function () {
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
secret: "secret"
|
secret: "secret"
|
||||||
},
|
},
|
||||||
ldap: {
|
authentication_backend: {
|
||||||
url: "http://ldap",
|
ldap: {
|
||||||
user: "user",
|
url: "http://ldap",
|
||||||
password: "password",
|
user: "user",
|
||||||
base_dn: "dc=example,dc=com"
|
password: "password",
|
||||||
|
base_dn: "dc=example,dc=com"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
notifier: {
|
notifier: {
|
||||||
email: {
|
email: {
|
||||||
|
|
|
@ -35,7 +35,10 @@ export default class Server {
|
||||||
const displayableConfiguration: Configuration = clone(configuration);
|
const displayableConfiguration: Configuration = clone(configuration);
|
||||||
const STARS = "*****";
|
const STARS = "*****";
|
||||||
|
|
||||||
displayableConfiguration.ldap.password = STARS;
|
if (displayableConfiguration.authentication_backend.ldap) {
|
||||||
|
displayableConfiguration.authentication_backend.ldap.password = STARS;
|
||||||
|
}
|
||||||
|
|
||||||
displayableConfiguration.session.secret = STARS;
|
displayableConfiguration.session.secret = STARS;
|
||||||
if (displayableConfiguration.notifier && displayableConfiguration.notifier.email)
|
if (displayableConfiguration.notifier && displayableConfiguration.notifier.email)
|
||||||
displayableConfiguration.notifier.email.password = STARS;
|
displayableConfiguration.notifier.email.password = STARS;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { INotifier } from "./notifiers/INotifier";
|
||||||
import { IRegulator } from "./regulation/IRegulator";
|
import { IRegulator } from "./regulation/IRegulator";
|
||||||
import { Configuration } from "./configuration/schema/Configuration";
|
import { Configuration } from "./configuration/schema/Configuration";
|
||||||
import { IAccessController } from "./access_control/IAccessController";
|
import { IAccessController } from "./access_control/IAccessController";
|
||||||
import { IUsersDatabase } from "./ldap/IUsersDatabase";
|
import { IUsersDatabase } from "./authentication/backends/IUsersDatabase";
|
||||||
|
|
||||||
export interface ServerVariables {
|
export interface ServerVariables {
|
||||||
logger: IRequestLogger;
|
logger: IRequestLogger;
|
||||||
|
|
|
@ -11,8 +11,8 @@ import { TotpHandler } from "./authentication/totp/TotpHandler";
|
||||||
import { ITotpHandler } from "./authentication/totp/ITotpHandler";
|
import { ITotpHandler } from "./authentication/totp/ITotpHandler";
|
||||||
import { NotifierFactory } from "./notifiers/NotifierFactory";
|
import { NotifierFactory } from "./notifiers/NotifierFactory";
|
||||||
import { MailSenderBuilder } from "./notifiers/MailSenderBuilder";
|
import { MailSenderBuilder } from "./notifiers/MailSenderBuilder";
|
||||||
import { LdapUsersDatabase } from "./ldap/LdapUsersDatabase";
|
import { LdapUsersDatabase } from "./authentication/backends/ldap/LdapUsersDatabase";
|
||||||
import { ConnectorFactory } from "./ldap/connector/ConnectorFactory";
|
import { ConnectorFactory } from "./authentication/backends/ldap/connector/ConnectorFactory";
|
||||||
|
|
||||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||||
import { UserDataStore } from "./storage/UserDataStore";
|
import { UserDataStore } from "./storage/UserDataStore";
|
||||||
|
@ -32,7 +32,9 @@ import { ServerVariables } from "./ServerVariables";
|
||||||
import { MethodCalculator } from "./authentication/MethodCalculator";
|
import { MethodCalculator } from "./authentication/MethodCalculator";
|
||||||
import { MongoClient } from "./connectors/mongo/MongoClient";
|
import { MongoClient } from "./connectors/mongo/MongoClient";
|
||||||
import { IGlobalLogger } from "./logging/IGlobalLogger";
|
import { IGlobalLogger } from "./logging/IGlobalLogger";
|
||||||
import { SessionFactory } from "./ldap/SessionFactory";
|
import { SessionFactory } from "./authentication/backends/ldap/SessionFactory";
|
||||||
|
import { IUsersDatabase } from "./authentication/backends/IUsersDatabase";
|
||||||
|
import { FileUsersDatabase } from "./authentication/backends/file/FileUsersDatabase";
|
||||||
|
|
||||||
class UserDataStoreFactory {
|
class UserDataStoreFactory {
|
||||||
static create(config: Configuration.Configuration, globalLogger: IGlobalLogger): BluebirdPromise<UserDataStore> {
|
static create(config: Configuration.Configuration, globalLogger: IGlobalLogger): BluebirdPromise<UserDataStore> {
|
||||||
|
@ -58,24 +60,44 @@ class UserDataStoreFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ServerVariablesInitializer {
|
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(
|
static initialize(
|
||||||
config: Configuration.Configuration,
|
config: Configuration.Configuration,
|
||||||
globalLogger: IGlobalLogger,
|
globalLogger: IGlobalLogger,
|
||||||
requestLogger: IRequestLogger,
|
requestLogger: IRequestLogger,
|
||||||
deps: GlobalDependencies): BluebirdPromise<ServerVariables> {
|
deps: GlobalDependencies)
|
||||||
|
: BluebirdPromise<ServerVariables> {
|
||||||
|
|
||||||
const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
|
const mailSenderBuilder =
|
||||||
const notifier = NotifierFactory.build(config.notifier, mailSenderBuilder);
|
new MailSenderBuilder(Nodemailer);
|
||||||
const ldapUsersDatabase = new LdapUsersDatabase(
|
const notifier = NotifierFactory.build(
|
||||||
new SessionFactory(
|
config.notifier, mailSenderBuilder);
|
||||||
config.ldap,
|
const accessController = new AccessController(
|
||||||
new ConnectorFactory(config.ldap, deps.ldapjs),
|
config.access_control, deps.winston);
|
||||||
deps.winston
|
const totpHandler = new TotpHandler(
|
||||||
),
|
deps.speakeasy);
|
||||||
config.ldap
|
const usersDatabase = this.createUsersDatabase(
|
||||||
);
|
config, deps);
|
||||||
const accessController = new AccessController(config.access_control, deps.winston);
|
|
||||||
const totpHandler = new TotpHandler(deps.speakeasy);
|
|
||||||
|
|
||||||
return UserDataStoreFactory.create(config, globalLogger)
|
return UserDataStoreFactory.create(config, globalLogger)
|
||||||
.then(function (userDataStore: UserDataStore) {
|
.then(function (userDataStore: UserDataStore) {
|
||||||
|
@ -85,7 +107,7 @@ export class ServerVariablesInitializer {
|
||||||
const variables: ServerVariables = {
|
const variables: ServerVariables = {
|
||||||
accessController: accessController,
|
accessController: accessController,
|
||||||
config: config,
|
config: config,
|
||||||
usersDatabase: ldapUsersDatabase,
|
usersDatabase: usersDatabase,
|
||||||
logger: requestLogger,
|
logger: requestLogger,
|
||||||
notifier: notifier,
|
notifier: notifier,
|
||||||
regulator: regulator,
|
regulator: regulator,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ServerVariables } from "./ServerVariables";
|
import { ServerVariables } from "./ServerVariables";
|
||||||
|
|
||||||
import { Configuration } from "./configuration/schema/Configuration";
|
import { Configuration } from "./configuration/schema/Configuration";
|
||||||
import { UsersDatabaseStub } from "./ldap/UsersDatabaseStub.spec";
|
import { IUsersDatabaseStub } from "./authentication/backends/IUsersDatabaseStub.spec";
|
||||||
import { AccessControllerStub } from "./access_control/AccessControllerStub.spec";
|
import { AccessControllerStub } from "./access_control/AccessControllerStub.spec";
|
||||||
import { RequestLoggerStub } from "./logging/RequestLoggerStub.spec";
|
import { RequestLoggerStub } from "./logging/RequestLoggerStub.spec";
|
||||||
import { NotifierStub } from "./notifiers/NotifierStub.spec";
|
import { NotifierStub } from "./notifiers/NotifierStub.spec";
|
||||||
|
@ -13,7 +13,7 @@ import { U2fHandlerStub } from "./authentication/u2f/U2fHandlerStub.spec";
|
||||||
export interface ServerVariablesMock {
|
export interface ServerVariablesMock {
|
||||||
accessController: AccessControllerStub;
|
accessController: AccessControllerStub;
|
||||||
config: Configuration;
|
config: Configuration;
|
||||||
usersDatabase: UsersDatabaseStub;
|
usersDatabase: IUsersDatabaseStub;
|
||||||
logger: RequestLoggerStub;
|
logger: RequestLoggerStub;
|
||||||
notifier: NotifierStub;
|
notifier: NotifierStub;
|
||||||
regulator: RegulatorStub;
|
regulator: RegulatorStub;
|
||||||
|
@ -34,17 +34,19 @@ export class ServerVariablesMockBuilder {
|
||||||
totp: {
|
totp: {
|
||||||
issuer: "authelia.com"
|
issuer: "authelia.com"
|
||||||
},
|
},
|
||||||
ldap: {
|
authentication_backend: {
|
||||||
url: "ldap://ldap",
|
ldap: {
|
||||||
base_dn: "dc=example,dc=com",
|
url: "ldap://ldap",
|
||||||
user: "user",
|
base_dn: "dc=example,dc=com",
|
||||||
password: "password",
|
user: "user",
|
||||||
mail_attribute: "mail",
|
password: "password",
|
||||||
additional_users_dn: "ou=users",
|
mail_attribute: "mail",
|
||||||
additional_groups_dn: "ou=groups",
|
additional_users_dn: "ou=users",
|
||||||
users_filter: "cn={0}",
|
additional_groups_dn: "ou=groups",
|
||||||
groups_filter: "member={dn}",
|
users_filter: "cn={0}",
|
||||||
group_name_attribute: "cn"
|
groups_filter: "member={dn}",
|
||||||
|
group_name_attribute: "cn"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
logs_level: "debug",
|
logs_level: "debug",
|
||||||
notifier: {},
|
notifier: {},
|
||||||
|
@ -60,7 +62,7 @@ export class ServerVariablesMockBuilder {
|
||||||
},
|
},
|
||||||
storage: {}
|
storage: {}
|
||||||
},
|
},
|
||||||
usersDatabase: new UsersDatabaseStub(),
|
usersDatabase: new IUsersDatabaseStub(),
|
||||||
logger: new RequestLoggerStub(enableLogging),
|
logger: new RequestLoggerStub(enableLogging),
|
||||||
notifier: new NotifierStub(),
|
notifier: new NotifierStub(),
|
||||||
regulator: new RegulatorStub(),
|
regulator: new RegulatorStub(),
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
export interface GroupsAndEmails {
|
||||||
|
groups: string[];
|
||||||
|
emails: string[];
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import Bluebird = require("bluebird");
|
import Bluebird = require("bluebird");
|
||||||
import { GroupsAndEmails } from "./ISession";
|
|
||||||
|
import { GroupsAndEmails } from "./GroupsAndEmails";
|
||||||
|
|
||||||
export interface IUsersDatabase {
|
export interface IUsersDatabase {
|
||||||
checkUserPassword(username: string, password: string): Bluebird<GroupsAndEmails>;
|
checkUserPassword(username: string, password: string): Bluebird<GroupsAndEmails>;
|
|
@ -2,9 +2,9 @@ import Bluebird = require("bluebird");
|
||||||
import Sinon = require("sinon");
|
import Sinon = require("sinon");
|
||||||
|
|
||||||
import { IUsersDatabase } from "./IUsersDatabase";
|
import { IUsersDatabase } from "./IUsersDatabase";
|
||||||
import { GroupsAndEmails } from "./ISession";
|
import { GroupsAndEmails } from "./GroupsAndEmails";
|
||||||
|
|
||||||
export class UsersDatabaseStub implements IUsersDatabase {
|
export class IUsersDatabaseStub implements IUsersDatabase {
|
||||||
checkUserPasswordStub: Sinon.SinonStub;
|
checkUserPasswordStub: Sinon.SinonStub;
|
||||||
getEmailsStub: Sinon.SinonStub;
|
getEmailsStub: Sinon.SinonStub;
|
||||||
getGroupsStub: Sinon.SinonStub;
|
getGroupsStub: Sinon.SinonStub;
|
|
@ -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());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,6 @@
|
||||||
|
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
|
||||||
export interface GroupsAndEmails {
|
|
||||||
groups: string[];
|
|
||||||
emails: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISession {
|
export interface ISession {
|
||||||
open(): BluebirdPromise<void>;
|
open(): BluebirdPromise<void>;
|
||||||
close(): BluebirdPromise<void>;
|
close(): BluebirdPromise<void>;
|
|
@ -1,9 +1,10 @@
|
||||||
import Bluebird = require("bluebird");
|
import Bluebird = require("bluebird");
|
||||||
import { IUsersDatabase } from "./IUsersDatabase";
|
import { IUsersDatabase } from "../IUsersDatabase";
|
||||||
import { ISessionFactory } from "./ISessionFactory";
|
import { ISessionFactory } from "./ISessionFactory";
|
||||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
|
||||||
import { ISession, GroupsAndEmails } from "./ISession";
|
import { ISession } from "./ISession";
|
||||||
import Exceptions = require("../Exceptions");
|
import { GroupsAndEmails } from "../GroupsAndEmails";
|
||||||
|
import Exceptions = require("../../../Exceptions");
|
||||||
|
|
||||||
type SessionCallback<T> = (session: ISession) => Bluebird<T>;
|
type SessionCallback<T> = (session: ISession) => Bluebird<T>;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import { ISession, GroupsAndEmails } from "./ISession";
|
import { ISession } from "./ISession";
|
||||||
import { Sanitizer } from "./Sanitizer";
|
import { Sanitizer } from "./Sanitizer";
|
||||||
|
|
||||||
const SPECIAL_CHAR_USED_MESSAGE = "Special character used in LDAP query.";
|
const SPECIAL_CHAR_USED_MESSAGE = "Special character used in LDAP query.";
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
|
||||||
import { Session } from "./Session";
|
import { Session } from "./Session";
|
||||||
import { ConnectorFactoryStub } from "./connector/ConnectorFactoryStub.spec";
|
import { ConnectorFactoryStub } from "./connector/ConnectorFactoryStub.spec";
|
||||||
import { ConnectorStub } from "./connector/ConnectorStub.spec";
|
import { ConnectorStub } from "./connector/ConnectorStub.spec";
|
|
@ -1,11 +1,11 @@
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import exceptions = require("../Exceptions");
|
import exceptions = require("../../../Exceptions");
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { ISession, GroupsAndEmails } from "./ISession";
|
import { ISession } from "./ISession";
|
||||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
|
||||||
import { Winston } from "../../../types/Dependencies";
|
import { Winston } from "../../../../../types/Dependencies";
|
||||||
import Util = require("util");
|
import Util = require("util");
|
||||||
import { HashGenerator } from "../utils/HashGenerator";
|
import { HashGenerator } from "../../../utils/HashGenerator";
|
||||||
import { IConnector } from "./connector/IConnector";
|
import { IConnector } from "./connector/IConnector";
|
||||||
|
|
||||||
export class Session implements ISession {
|
export class Session implements ISession {
|
|
@ -4,7 +4,7 @@ import Winston = require("winston");
|
||||||
import { IConnectorFactory } from "./connector/IConnectorFactory";
|
import { IConnectorFactory } from "./connector/IConnectorFactory";
|
||||||
import { ISessionFactory } from "./ISessionFactory";
|
import { ISessionFactory } from "./ISessionFactory";
|
||||||
import { ISession } from "./ISession";
|
import { ISession } from "./ISession";
|
||||||
import { LdapConfiguration } from "../configuration/schema/LdapConfiguration";
|
import { LdapConfiguration } from "../../../configuration/schema/LdapConfiguration";
|
||||||
import { Session } from "./Session";
|
import { Session } from "./Session";
|
||||||
import { SafeSession } from "./SafeSession";
|
import { SafeSession } from "./SafeSession";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Bluebird = require("bluebird");
|
import Bluebird = require("bluebird");
|
||||||
import Sinon = require("sinon");
|
import Sinon = require("sinon");
|
||||||
|
|
||||||
import { ISession, GroupsAndEmails } from "./ISession";
|
import { ISession } from "./ISession";
|
||||||
|
|
||||||
export class SessionStub implements ISession {
|
export class SessionStub implements ISession {
|
||||||
openStub: Sinon.SinonStub;
|
openStub: Sinon.SinonStub;
|
|
@ -2,7 +2,7 @@ import LdapJs = require("ldapjs");
|
||||||
import EventEmitter = require("events");
|
import EventEmitter = require("events");
|
||||||
import Bluebird = require("bluebird");
|
import Bluebird = require("bluebird");
|
||||||
import { IConnector } from "./IConnector";
|
import { IConnector } from "./IConnector";
|
||||||
import Exceptions = require("../../Exceptions");
|
import Exceptions = require("../../../../Exceptions");
|
||||||
|
|
||||||
interface SearchEntry {
|
interface SearchEntry {
|
||||||
object: any;
|
object: any;
|
|
@ -1,6 +1,6 @@
|
||||||
import { IConnector } from "./IConnector";
|
import { IConnector } from "./IConnector";
|
||||||
import { Connector } from "./Connector";
|
import { Connector } from "./Connector";
|
||||||
import { LdapConfiguration } from "../../configuration/schema/LdapConfiguration";
|
import { LdapConfiguration } from "../../../../configuration/schema/LdapConfiguration";
|
||||||
import { Ldapjs } from "Dependencies";
|
import { Ldapjs } from "Dependencies";
|
||||||
|
|
||||||
export class ConnectorFactory {
|
export class ConnectorFactory {
|
|
@ -1,5 +1,6 @@
|
||||||
import Sinon = require("sinon");
|
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import Sinon = require("sinon");
|
||||||
|
|
||||||
import { IConnectorFactory } from "./IConnectorFactory";
|
import { IConnectorFactory } from "./IConnectorFactory";
|
||||||
import { IConnector } from "./IConnector";
|
import { IConnector } from "./IConnector";
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Sinon = require("sinon");
|
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import Sinon = require("sinon");
|
||||||
|
|
||||||
import { IConnector } from "./IConnector";
|
import { IConnector } from "./IConnector";
|
||||||
|
|
||||||
export class ConnectorStub implements IConnector {
|
export class ConnectorStub implements IConnector {
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import Bluebird = require("bluebird");
|
import Bluebird = require("bluebird");
|
||||||
import EventEmitter = require("events");
|
import EventEmitter = require("events");
|
||||||
|
|
|
@ -7,13 +7,15 @@ describe("configuration/ConfigurationParser", function () {
|
||||||
function buildYamlConfig(): Configuration {
|
function buildYamlConfig(): Configuration {
|
||||||
const yaml_config: Configuration = {
|
const yaml_config: Configuration = {
|
||||||
port: 8080,
|
port: 8080,
|
||||||
ldap: {
|
authentication_backend: {
|
||||||
url: "http://ldap",
|
ldap: {
|
||||||
base_dn: "dc=example,dc=com",
|
url: "http://ldap",
|
||||||
additional_users_dn: "ou=users",
|
base_dn: "dc=example,dc=com",
|
||||||
additional_groups_dn: "ou=groups",
|
additional_users_dn: "ou=users",
|
||||||
user: "user",
|
additional_groups_dn: "ou=groups",
|
||||||
password: "pass"
|
user: "user",
|
||||||
|
password: "pass"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
|
|
|
@ -19,17 +19,19 @@ describe("configuration/SessionConfigurationBuilder", function () {
|
||||||
totp: {
|
totp: {
|
||||||
issuer: "authelia.com"
|
issuer: "authelia.com"
|
||||||
},
|
},
|
||||||
ldap: {
|
authentication_backend: {
|
||||||
url: "ldap://ldap",
|
ldap: {
|
||||||
user: "user",
|
url: "ldap://ldap",
|
||||||
base_dn: "dc=example,dc=com",
|
user: "user",
|
||||||
password: "password",
|
base_dn: "dc=example,dc=com",
|
||||||
additional_groups_dn: "ou=groups",
|
password: "password",
|
||||||
additional_users_dn: "ou=users",
|
additional_groups_dn: "ou=groups",
|
||||||
group_name_attribute: "",
|
additional_users_dn: "ou=users",
|
||||||
groups_filter: "",
|
group_name_attribute: "",
|
||||||
mail_attribute: "",
|
groups_filter: "",
|
||||||
users_filter: ""
|
mail_attribute: "",
|
||||||
|
users_filter: ""
|
||||||
|
},
|
||||||
},
|
},
|
||||||
logs_level: "debug",
|
logs_level: "debug",
|
||||||
notifier: {
|
notifier: {
|
||||||
|
@ -100,17 +102,19 @@ describe("configuration/SessionConfigurationBuilder", function () {
|
||||||
totp: {
|
totp: {
|
||||||
issuer: "authelia.com"
|
issuer: "authelia.com"
|
||||||
},
|
},
|
||||||
ldap: {
|
authentication_backend: {
|
||||||
url: "ldap://ldap",
|
ldap: {
|
||||||
user: "user",
|
url: "ldap://ldap",
|
||||||
password: "password",
|
user: "user",
|
||||||
base_dn: "dc=example,dc=com",
|
password: "password",
|
||||||
additional_groups_dn: "ou=groups",
|
base_dn: "dc=example,dc=com",
|
||||||
additional_users_dn: "ou=users",
|
additional_groups_dn: "ou=groups",
|
||||||
group_name_attribute: "",
|
additional_users_dn: "ou=users",
|
||||||
groups_filter: "",
|
group_name_attribute: "",
|
||||||
mail_attribute: "",
|
groups_filter: "",
|
||||||
users_filter: ""
|
mail_attribute: "",
|
||||||
|
users_filter: ""
|
||||||
|
},
|
||||||
},
|
},
|
||||||
logs_level: "debug",
|
logs_level: "debug",
|
||||||
notifier: {
|
notifier: {
|
||||||
|
|
|
@ -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`");
|
||||||
|
});
|
||||||
|
});
|
|
@ -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];
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { ACLConfiguration, complete as AclConfigurationComplete } from "./AclConfiguration";
|
import { ACLConfiguration, complete as AclConfigurationComplete } from "./AclConfiguration";
|
||||||
import { AuthenticationMethodsConfiguration, complete as AuthenticationMethodsConfigurationComplete } from "./AuthenticationMethodsConfiguration";
|
import { AuthenticationMethodsConfiguration, complete as AuthenticationMethodsConfigurationComplete } from "./AuthenticationMethodsConfiguration";
|
||||||
import { LdapConfiguration, complete as LdapConfigurationComplete } from "./LdapConfiguration";
|
import { AuthenticationBackendConfiguration, complete as AuthenticationBackendComplete } from "./AuthenticationBackendConfiguration";
|
||||||
import { NotifierConfiguration, complete as NotifierConfigurationComplete } from "./NotifierConfiguration";
|
import { NotifierConfiguration, complete as NotifierConfigurationComplete } from "./NotifierConfiguration";
|
||||||
import { RegulationConfiguration, complete as RegulationConfigurationComplete } from "./RegulationConfiguration";
|
import { RegulationConfiguration, complete as RegulationConfigurationComplete } from "./RegulationConfiguration";
|
||||||
import { SessionConfiguration, complete as SessionConfigurationComplete } from "./SessionConfiguration";
|
import { SessionConfiguration, complete as SessionConfigurationComplete } from "./SessionConfiguration";
|
||||||
|
@ -10,7 +10,7 @@ import { MethodCalculator } from "../../authentication/MethodCalculator";
|
||||||
|
|
||||||
export interface Configuration {
|
export interface Configuration {
|
||||||
access_control?: ACLConfiguration;
|
access_control?: ACLConfiguration;
|
||||||
ldap: LdapConfiguration;
|
authentication_backend: AuthenticationBackendConfiguration;
|
||||||
authentication_methods?: AuthenticationMethodsConfiguration;
|
authentication_methods?: AuthenticationMethodsConfiguration;
|
||||||
default_redirection_url?: string;
|
default_redirection_url?: string;
|
||||||
logs_level?: string;
|
logs_level?: string;
|
||||||
|
@ -30,10 +30,16 @@ export function complete(
|
||||||
JSON.stringify(configuration));
|
JSON.stringify(configuration));
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
newConfiguration.access_control = AclConfigurationComplete(
|
newConfiguration.access_control =
|
||||||
newConfiguration.access_control);
|
AclConfigurationComplete(
|
||||||
newConfiguration.ldap = LdapConfigurationComplete(
|
newConfiguration.access_control);
|
||||||
newConfiguration.ldap);
|
|
||||||
|
const [backend, error] =
|
||||||
|
AuthenticationBackendComplete(
|
||||||
|
newConfiguration.authentication_backend);
|
||||||
|
|
||||||
|
if (error) errors.push(error);
|
||||||
|
newConfiguration.authentication_backend = backend;
|
||||||
|
|
||||||
newConfiguration.authentication_methods =
|
newConfiguration.authentication_methods =
|
||||||
AuthenticationMethodsConfigurationComplete(
|
AuthenticationMethodsConfigurationComplete(
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
export interface FileUsersDatabaseConfiguration {
|
||||||
|
path: string;
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ import UserMessages = require("../../../../../shared/UserMessages");
|
||||||
import { MethodCalculator } from "../../authentication/MethodCalculator";
|
import { MethodCalculator } from "../../authentication/MethodCalculator";
|
||||||
import { ServerVariables } from "../../ServerVariables";
|
import { ServerVariables } from "../../ServerVariables";
|
||||||
import { AuthenticationSession } from "../../../../types/AuthenticationSession";
|
import { AuthenticationSession } from "../../../../types/AuthenticationSession";
|
||||||
import { GroupsAndEmails } from "../../ldap/ISession";
|
import { GroupsAndEmails } from "../../authentication/backends/GroupsAndEmails";
|
||||||
|
|
||||||
export default function (vars: ServerVariables) {
|
export default function (vars: ServerVariables) {
|
||||||
return function (req: express.Request, res: express.Response)
|
return function (req: express.Request, res: express.Response)
|
||||||
|
|
|
@ -42,7 +42,7 @@ describe("routes/password-reset/form/post", function () {
|
||||||
mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
|
mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
|
||||||
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
|
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
|
||||||
|
|
||||||
mocks.config.ldap = {
|
mocks.config.authentication_backend.ldap = {
|
||||||
url: "ldap://ldapjs",
|
url: "ldap://ldapjs",
|
||||||
mail_attribute: "mail",
|
mail_attribute: "mail",
|
||||||
user: "user",
|
user: "user",
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { IdentityValidable } from "../../../IdentityValidable";
|
||||||
import { PRE_VALIDATION_TEMPLATE } from "../../../IdentityCheckPreValidationTemplate";
|
import { PRE_VALIDATION_TEMPLATE } from "../../../IdentityCheckPreValidationTemplate";
|
||||||
import Constants = require("../constants");
|
import Constants = require("../constants");
|
||||||
import { IRequestLogger } from "../../../logging/IRequestLogger";
|
import { IRequestLogger } from "../../../logging/IRequestLogger";
|
||||||
import { IUsersDatabase } from "../../../ldap/IUsersDatabase";
|
import { IUsersDatabase } from "../../../authentication/backends/IUsersDatabase";
|
||||||
|
|
||||||
export const TEMPLATE_NAME = "password-reset-form";
|
export const TEMPLATE_NAME = "password-reset-form";
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,14 @@ import { HashGenerator } from "./HashGenerator";
|
||||||
|
|
||||||
describe("utils/HashGenerator", function () {
|
describe("utils/HashGenerator", function () {
|
||||||
it("should compute correct ssha512 (password)", function () {
|
it("should compute correct ssha512 (password)", function () {
|
||||||
return HashGenerator.ssha512("password", "jgiCMRyGXzoqpxS3")
|
return HashGenerator.ssha512("password", 500000, "jgiCMRyGXzoqpxS3")
|
||||||
.then(function (hash: string) {
|
.then(function (hash: string) {
|
||||||
Assert.equal(hash, "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/");
|
Assert.equal(hash, "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should compute correct ssha512 (test)", function () {
|
it("should compute correct ssha512 (test)", function () {
|
||||||
return HashGenerator.ssha512("test", "abcdefghijklmnop")
|
return HashGenerator.ssha512("test", 500000, "abcdefghijklmnop")
|
||||||
.then(function (hash: string) {
|
.then(function (hash: string) {
|
||||||
Assert.equal(hash, "{CRYPT}$6$rounds=500000$abcdefghijklmnop$sTlNGf0VO/HTQIOXemmaBbV28HUch/qhWOA1/4dsDj6CDQYhUgXbYSPL6gccAsWMr2zD5fFWwhKmPdG.yxphs.");
|
Assert.equal(hash, "{CRYPT}$6$rounds=500000$abcdefghijklmnop$sTlNGf0VO/HTQIOXemmaBbV28HUch/qhWOA1/4dsDj6CDQYhUgXbYSPL6gccAsWMr2zD5fFWwhKmPdG.yxphs.");
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,8 +4,10 @@ import Util = require("util");
|
||||||
const crypt = require("crypt3");
|
const crypt = require("crypt3");
|
||||||
|
|
||||||
export class HashGenerator {
|
export class HashGenerator {
|
||||||
static ssha512(password: string, salt?: string): BluebirdPromise<string> {
|
static ssha512(
|
||||||
const rounds = 500000;
|
password: string,
|
||||||
|
rounds: number = 500000,
|
||||||
|
salt?: string): BluebirdPromise<string> {
|
||||||
const saltSize = 16;
|
const saltSize = 16;
|
||||||
// $6 means SHA512
|
// $6 means SHA512
|
||||||
const _salt = Util.format("$6$rounds=%d$%s", rounds,
|
const _salt = Util.format("$6$rounds=%d$%s", rounds,
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import SeleniumWebdriver = require("selenium-webdriver");
|
||||||
|
|
||||||
|
export default function(driver: any, linkText: string) {
|
||||||
|
return driver.wait(
|
||||||
|
SeleniumWebdriver.until.elementLocated(
|
||||||
|
SeleniumWebdriver.By.linkText(linkText)), 5000)
|
||||||
|
.then(function (el) {
|
||||||
|
return el.click();
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import SeleniumWebdriver = require("selenium-webdriver");
|
||||||
|
|
||||||
|
export default function(driver: any, fieldName: string, text: string) {
|
||||||
|
return driver.wait(
|
||||||
|
SeleniumWebdriver.until.elementLocated(
|
||||||
|
SeleniumWebdriver.By.name(fieldName)), 5000)
|
||||||
|
.then(function (el) {
|
||||||
|
return el.sendKeys(text);
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,34 @@
|
||||||
|
import Bluebird = require("bluebird");
|
||||||
|
import Fs = require("fs");
|
||||||
|
import Request = require("request-promise");
|
||||||
|
|
||||||
|
export function GetLinkFromFile(): Bluebird<string> {
|
||||||
|
return Bluebird.promisify(Fs.readFile)("/tmp/authelia/notification.txt")
|
||||||
|
.then(function (data: any) {
|
||||||
|
const regexp = new RegExp(/Link: (.+)/);
|
||||||
|
const match = regexp.exec(data);
|
||||||
|
const link = match[1];
|
||||||
|
return Bluebird.resolve(link);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export function GetLinkFromEmail(): Bluebird<string> {
|
||||||
|
return Request({
|
||||||
|
method: "GET",
|
||||||
|
uri: "http://localhost:8085/messages",
|
||||||
|
json: true
|
||||||
|
})
|
||||||
|
.then(function (data: any) {
|
||||||
|
const messageId = data[data.length - 1].id;
|
||||||
|
return Request({
|
||||||
|
method: "GET",
|
||||||
|
uri: `http://localhost:8085/messages/${messageId}.html`
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function (data: any) {
|
||||||
|
const regexp = new RegExp(/<a href="(.+)" class="button">Continue<\/a>/);
|
||||||
|
const match = regexp.exec(data);
|
||||||
|
const link = match[1];
|
||||||
|
return Bluebird.resolve(link);
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,38 +1,6 @@
|
||||||
import Bluebird = require("bluebird");
|
import Bluebird = require("bluebird");
|
||||||
import SeleniumWebdriver = require("selenium-webdriver");
|
import SeleniumWebdriver = require("selenium-webdriver");
|
||||||
import Fs = require("fs");
|
import {GetLinkFromFile, GetLinkFromEmail} from '../helpers/get-identity-link';
|
||||||
import Request = require("request-promise");
|
|
||||||
|
|
||||||
function retrieveValidationLinkFromNotificationFile(): Bluebird<string> {
|
|
||||||
return Bluebird.promisify(Fs.readFile)("/tmp/authelia/notification.txt")
|
|
||||||
.then(function (data: any) {
|
|
||||||
const regexp = new RegExp(/Link: (.+)/);
|
|
||||||
const match = regexp.exec(data);
|
|
||||||
const link = match[1];
|
|
||||||
return Bluebird.resolve(link);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function retrieveValidationLinkFromEmail(): Bluebird<string> {
|
|
||||||
return Request({
|
|
||||||
method: "GET",
|
|
||||||
uri: "http://localhost:8085/messages",
|
|
||||||
json: true
|
|
||||||
})
|
|
||||||
.then(function (data: any) {
|
|
||||||
const messageId = data[data.length - 1].id;
|
|
||||||
return Request({
|
|
||||||
method: "GET",
|
|
||||||
uri: `http://localhost:8085/messages/${messageId}.html`
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(function (data: any) {
|
|
||||||
const regexp = new RegExp(/<a href="(.+)" class="button">Continue<\/a>/);
|
|
||||||
const match = regexp.exec(data);
|
|
||||||
const link = match[1];
|
|
||||||
return Bluebird.resolve(link);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function(driver: any, email?: boolean): Bluebird<string> {
|
export default function(driver: any, email?: boolean): Bluebird<string> {
|
||||||
return driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.className("register-totp")), 5000)
|
return driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.className("register-totp")), 5000)
|
||||||
|
@ -40,8 +8,8 @@ export default function(driver: any, email?: boolean): Bluebird<string> {
|
||||||
return driver.findElement(SeleniumWebdriver.By.className("register-totp")).click();
|
return driver.findElement(SeleniumWebdriver.By.className("register-totp")).click();
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
if(email) return retrieveValidationLinkFromEmail();
|
if(email) return GetLinkFromEmail();
|
||||||
else return retrieveValidationLinkFromNotificationFile();
|
else return GetLinkFromFile();
|
||||||
})
|
})
|
||||||
.then(function (link: string) {
|
.then(function (link: string) {
|
||||||
return driver.get(link);
|
return driver.get(link);
|
||||||
|
|
|
@ -1,21 +1,32 @@
|
||||||
require("chromedriver");
|
require("chromedriver");
|
||||||
|
import ChildProcess = require('child_process');
|
||||||
|
import Bluebird = require("bluebird");
|
||||||
|
|
||||||
import Environment = require('../environment');
|
import Environment = require('../environment');
|
||||||
|
|
||||||
|
const execAsync = Bluebird.promisify(ChildProcess.exec);
|
||||||
|
|
||||||
const includes = [
|
const includes = [
|
||||||
"docker-compose.minimal.yml",
|
"docker-compose.minimal.yml",
|
||||||
"example/compose/docker-compose.base.yml",
|
"example/compose/docker-compose.base.yml",
|
||||||
"example/compose/nginx/minimal/docker-compose.yml",
|
"example/compose/nginx/minimal/docker-compose.yml",
|
||||||
"example/compose/ldap/docker-compose.yml"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
before(function() {
|
before(function() {
|
||||||
this.timeout(20000);
|
this.timeout(20000);
|
||||||
this.environment = new Environment.Environment(includes);
|
this.environment = new Environment.Environment(includes);
|
||||||
return this.environment.setup(2000);
|
|
||||||
|
return execAsync("cp users_database.yml users_database.test.yml")
|
||||||
|
.then(() => this.environment.setup(2000));
|
||||||
});
|
});
|
||||||
|
|
||||||
after(function() {
|
after(function() {
|
||||||
this.timeout(30000);
|
this.timeout(30000);
|
||||||
return this.environment.cleanup();
|
return execAsync("rm users_database.test.yml")
|
||||||
|
.then(() => {
|
||||||
|
if(process.env.KEEP_ENV != "true") {
|
||||||
|
return this.environment.cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -0,0 +1,43 @@
|
||||||
|
require("chromedriver");
|
||||||
|
import Bluebird = require("bluebird");
|
||||||
|
import ChildProcess = require("child_process");
|
||||||
|
import SeleniumWebdriver = require("selenium-webdriver");
|
||||||
|
|
||||||
|
import WithDriver from '../helpers/with-driver';
|
||||||
|
import VisitPage from '../helpers/visit-page';
|
||||||
|
import ClickOnLink from '../helpers/click-on-link';
|
||||||
|
import ClickOnButton from '../helpers/click-on-button';
|
||||||
|
import WaitRedirect from '../helpers/wait-redirected';
|
||||||
|
import FillField from "../helpers/fill-field";
|
||||||
|
import {GetLinkFromFile} from "../helpers/get-identity-link";
|
||||||
|
import FillLoginPageAndClick from "../helpers/fill-login-page-and-click";
|
||||||
|
|
||||||
|
const execAsync = Bluebird.promisify(ChildProcess.exec);
|
||||||
|
|
||||||
|
describe('Reset password', function() {
|
||||||
|
this.timeout(10000);
|
||||||
|
WithDriver();
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
return execAsync("cp users_database.yml users_database.test.yml");
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('click on reset password', function() {
|
||||||
|
it("should reset password for john", function() {
|
||||||
|
return VisitPage(this.driver, "https://login.example.com:8080/")
|
||||||
|
.then(() => ClickOnLink(this.driver, "Forgot password\?"))
|
||||||
|
.then(() => WaitRedirect(this.driver, "https://login.example.com:8080/password-reset/request"))
|
||||||
|
.then(() => FillField(this.driver, "username", "john"))
|
||||||
|
.then(() => ClickOnButton(this.driver, "Reset Password"))
|
||||||
|
.then(() => this.driver.sleep(1000)) // Simulate the time to read it from mailbox.
|
||||||
|
.then(() => GetLinkFromFile())
|
||||||
|
.then((link) => VisitPage(this.driver, link))
|
||||||
|
.then(() => FillField(this.driver, "password1", "newpass"))
|
||||||
|
.then(() => FillField(this.driver, "password2", "newpass"))
|
||||||
|
.then(() => ClickOnButton(this.driver, "Reset Password"))
|
||||||
|
.then(() => WaitRedirect(this.driver, "https://login.example.com:8080/"))
|
||||||
|
.then(() => FillLoginPageAndClick(this.driver, "john", "newpass"))
|
||||||
|
.then(() => WaitRedirect(this.driver, "https://login.example.com:8080/secondfactor"))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,29 @@
|
||||||
|
###############################################################
|
||||||
|
# Users Database #
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
# This file can be used if you do not have an LDAP set up.
|
||||||
|
|
||||||
|
# List of users
|
||||||
|
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: []
|
||||||
|
|
||||||
|
bob:
|
||||||
|
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||||
|
email: bob.dylan@authelia.com
|
||||||
|
groups:
|
||||||
|
- dev
|
||||||
|
|
||||||
|
james:
|
||||||
|
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||||
|
email: james.dean@authelia.com
|
Loading…
Reference in New Issue