Improve authelia-scripts to add suites with Docker-based Authelia server.

pull/330/head
Clement Michaud 2019-03-02 16:19:08 +01:00
parent 38271e3335
commit a1c9bb6302
40 changed files with 768 additions and 494 deletions

3
1 100644
View File

@ -0,0 +1,3 @@
Error: No such container: authelia-test
Error: No such container: 2
Error: No such container: dev/null

199
package-lock.json generated
View File

@ -231,6 +231,25 @@
"integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==",
"dev": true
},
"@types/chokidar": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/@types/chokidar/-/chokidar-1.7.5.tgz",
"integrity": "sha512-PDkSRY7KltW3M60hSBlerxI8SFPXsO3AL/aRVsO4Kh9IHRW74Ih75gUuTd/aE4LSSFqypb10UIX3QzOJwBQMGQ==",
"dev": true,
"requires": {
"@types/events": "1.2.0",
"@types/node": "10.0.3"
}
},
"@types/commander": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz",
"integrity": "sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==",
"dev": true,
"requires": {
"commander": "2.19.0"
}
},
"@types/connect": {
"version": "3.4.32",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
@ -2586,23 +2605,21 @@
"dev": true,
"optional": true,
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
"delegates": "1.0.0",
"readable-stream": "2.3.6"
}
},
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
@ -2615,20 +2632,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -2669,7 +2683,7 @@
"dev": true,
"optional": true,
"requires": {
"minipass": "^2.2.1"
"minipass": "2.2.4"
}
},
"fs.realpath": {
@ -2684,14 +2698,14 @@
"dev": true,
"optional": true,
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.0",
"object-assign": "^4.1.0",
"signal-exit": "^3.0.0",
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wide-align": "^1.1.0"
"aproba": "1.2.0",
"console-control-strings": "1.1.0",
"has-unicode": "2.0.1",
"object-assign": "4.1.1",
"signal-exit": "3.0.2",
"string-width": "1.0.2",
"strip-ansi": "3.0.1",
"wide-align": "1.1.2"
}
},
"glob": {
@ -2700,12 +2714,12 @@
"dev": true,
"optional": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"has-unicode": {
@ -2720,7 +2734,7 @@
"dev": true,
"optional": true,
"requires": {
"safer-buffer": "^2.1.0"
"safer-buffer": "2.1.2"
}
},
"ignore-walk": {
@ -2729,7 +2743,7 @@
"dev": true,
"optional": true,
"requires": {
"minimatch": "^3.0.4"
"minimatch": "3.0.4"
}
},
"inflight": {
@ -2738,15 +2752,14 @@
"dev": true,
"optional": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -2758,9 +2771,8 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
"number-is-nan": "1.0.1"
}
},
"isarray": {
@ -2773,25 +2785,22 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
"brace-expansion": "1.1.11"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
"safe-buffer": "5.1.1",
"yallist": "3.0.2"
}
},
"minizlib": {
@ -2800,14 +2809,13 @@
"dev": true,
"optional": true,
"requires": {
"minipass": "^2.2.1"
"minipass": "2.2.4"
}
},
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -2824,9 +2832,9 @@
"dev": true,
"optional": true,
"requires": {
"debug": "^2.1.2",
"iconv-lite": "^0.4.4",
"sax": "^1.2.4"
"debug": "2.6.9",
"iconv-lite": "0.4.21",
"sax": "1.2.4"
}
},
"node-pre-gyp": {
@ -2835,16 +2843,16 @@
"dev": true,
"optional": true,
"requires": {
"detect-libc": "^1.0.2",
"mkdirp": "^0.5.1",
"needle": "^2.2.0",
"nopt": "^4.0.1",
"npm-packlist": "^1.1.6",
"npmlog": "^4.0.2",
"rc": "^1.1.7",
"rimraf": "^2.6.1",
"semver": "^5.3.0",
"tar": "^4"
"detect-libc": "1.0.3",
"mkdirp": "0.5.1",
"needle": "2.2.0",
"nopt": "4.0.1",
"npm-packlist": "1.1.10",
"npmlog": "4.1.2",
"rc": "1.2.7",
"rimraf": "2.6.2",
"semver": "5.5.0",
"tar": "4.4.1"
}
},
"nopt": {
@ -2853,8 +2861,8 @@
"dev": true,
"optional": true,
"requires": {
"abbrev": "1",
"osenv": "^0.1.4"
"abbrev": "1.1.1",
"osenv": "0.1.5"
}
},
"npm-bundled": {
@ -2869,8 +2877,8 @@
"dev": true,
"optional": true,
"requires": {
"ignore-walk": "^3.0.1",
"npm-bundled": "^1.0.1"
"ignore-walk": "3.0.1",
"npm-bundled": "1.0.3"
}
},
"npmlog": {
@ -2879,17 +2887,16 @@
"dev": true,
"optional": true,
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
"gauge": "~2.7.3",
"set-blocking": "~2.0.0"
"are-we-there-yet": "1.1.4",
"console-control-strings": "1.1.0",
"gauge": "2.7.4",
"set-blocking": "2.0.0"
}
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -2901,9 +2908,8 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
"wrappy": "1.0.2"
}
},
"os-homedir": {
@ -2924,8 +2930,8 @@
"dev": true,
"optional": true,
"requires": {
"os-homedir": "^1.0.0",
"os-tmpdir": "^1.0.0"
"os-homedir": "1.0.2",
"os-tmpdir": "1.0.2"
}
},
"path-is-absolute": {
@ -2946,10 +2952,10 @@
"dev": true,
"optional": true,
"requires": {
"deep-extend": "^0.5.1",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
"deep-extend": "0.5.1",
"ini": "1.3.5",
"minimist": "1.2.0",
"strip-json-comments": "2.0.1"
},
"dependencies": {
"minimist": {
@ -2966,13 +2972,13 @@
"dev": true,
"optional": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "2.0.0",
"safe-buffer": "5.1.1",
"string_decoder": "1.1.1",
"util-deprecate": "1.0.2"
}
},
"rimraf": {
@ -2981,7 +2987,7 @@
"dev": true,
"optional": true,
"requires": {
"glob": "^7.0.5"
"glob": "7.1.2"
}
},
"safe-buffer": {
@ -3023,11 +3029,10 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
"strip-ansi": "3.0.1"
}
},
"string_decoder": {
@ -3036,7 +3041,7 @@
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "~5.1.0"
"safe-buffer": "5.1.1"
}
},
"strip-ansi": {
@ -3044,7 +3049,7 @@
"bundled": true,
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
"ansi-regex": "2.1.1"
}
},
"strip-json-comments": {
@ -3059,13 +3064,13 @@
"dev": true,
"optional": true,
"requires": {
"chownr": "^1.0.1",
"fs-minipass": "^1.2.5",
"minipass": "^2.2.4",
"minizlib": "^1.1.0",
"mkdirp": "^0.5.0",
"safe-buffer": "^5.1.1",
"yallist": "^3.0.2"
"chownr": "1.0.1",
"fs-minipass": "1.2.5",
"minipass": "2.2.4",
"minizlib": "1.1.0",
"mkdirp": "0.5.1",
"safe-buffer": "5.1.1",
"yallist": "3.0.2"
}
},
"util-deprecate": {
@ -3080,7 +3085,7 @@
"dev": true,
"optional": true,
"requires": {
"string-width": "^1.0.2"
"string-width": "1.0.2"
}
},
"wrappy": {

View File

@ -9,12 +9,13 @@
"node": ">=8.0.0 <10.0.0"
},
"scripts": {
"start": "./scripts/authelia-scripts start",
"start": "ts-node -P ./test/tsconfig.json ./scripts/authelia-scripts start",
"build": "./scripts/authelia-scripts build",
"unittest": "./scripts/authelia-scripts unittest",
"test": "./scripts/authelia-scripts test",
"travis": "./scripts/authelia-scripts travis",
"cover": "NODE_ENV=test nyc npm t"
"cover": "NODE_ENV=test nyc npm t",
"scripts": "./scripts/authelia-scripts"
},
"repository": {
"type": "git",
@ -60,6 +61,8 @@
"devDependencies": {
"@types/bluebird": "^3.5.4",
"@types/body-parser": "^1.16.3",
"@types/chokidar": "^1.7.5",
"@types/commander": "^2.12.2",
"@types/connect-redis": "0.0.7",
"@types/ejs": "^2.3.33",
"@types/express": "^4.0.35",

View File

@ -5,16 +5,13 @@ var program = require('commander');
program
.version('0.0.1')
.command('start', 'Start development environment.')
.command('suites', 'Run Authelia in specific environments.')
.command('serve', 'Run Authelia with a customized configuration.')
.command('build', 'Build production version of Authelia from source.')
.command('clean', 'Clean the production version of Authelia.')
.command('serve', 'Serve production version of Authelia.')
.command('test', 'Run Authelia integration tests.')
.command('unittest', 'Run Authelia integration tests.')
.command('travis', 'Build and test Authelia on Travis.')
.command('hash-password <password>', 'Hash a password with SSHA512.')
.command('docker', 'Docker related commands.')
.parse(process.argv);

View File

@ -3,8 +3,4 @@
var { exec } = require('./utils/exec');
var { IMAGE_NAME } = require('./utils/docker');
async function main() {
await exec(`docker build -t ${IMAGE_NAME} .`);
}
main();
exec(`docker build -t ${IMAGE_NAME} .`);

View File

@ -1,49 +1,27 @@
#!/usr/bin/env node
var program = require('commander');
var execSync = require('child_process').execSync;
var spawn = require('child_process').spawn;
var fs = require('fs');
let config;
program
.option('-c, --config <config>', 'Configuration file to run Authelia with.')
.option('--no-watch', 'Disable hot reload.')
.description('Run Authelia server with a custom configuration file. This is an alternative to suites in the case the environment is already set up.')
.arguments('[config_file]', 'Configuration file to run Authelia with.')
.action((configArg) => config = configArg)
.parse(process.argv);
let config = 'config.yml'; // set default config file.
if (program.config) {
config = program.config;
if (!config) {
config = 'config.yml'; // set default config file.;
}
// Render the production version of the nginx portal configuration
execSync('./example/compose/nginx/portal/render.js --production');
// Prepare the environment
execSync('./scripts/utils/prepare-environment.sh');
var server;
if (program.watch) {
server = spawn('./node_modules/.bin/nodemon',
['-e', 'yml', '--ignore', './users_database*.yml', '--exec', `node dist/server/src/index.js ${config}`]);
}
else {
server = spawn('/usr/bin/env', ['node', 'dist/server/src/index.js', config]);
}
var logStream = fs.createWriteStream('/tmp/authelia-server.log', {flags: 'a'});
server.stdout.on('data', (data) => {
process.stdout.write(`${data}`);
});
server.stdout.pipe(logStream);
server.stderr.on('data', (data) => {
process.stderr.write(`${data}`);
});
server.stderr.pipe(logStream);
const server = spawn('/usr/bin/env', ['node', 'dist/server/src/index.js', config]);
server.stdout.pipe(process.stdout);
server.stderr.pipe(process.stderr);
server.on('exit', function(statusCode) {
process.exit(statusCode);
})
});

View File

@ -1,162 +0,0 @@
#!/usr/bin/env node
var program = require('commander');
var spawn = require('child_process').spawn;
var chokidar = require('chokidar');
var fs = require('fs');
var kill = require('tree-kill');
program
.option('-s, --suite <suite>', 'The suite to run Authelia for. This suite represents a configuration for Authelia and a set of tests for that configuration.')
.parse(process.argv)
if (!program.suite) {
throw new Error('Please provide a suite.');
}
const ENVIRONMENT_FILENAME = '.suite';
const AUTHELIA_INTERRUPT_FILENAME = '.authelia-interrupt';
const CONFIG_FILEPATH = `test/suites/${program.suite}/config.yml`;
var tsWatcher = chokidar.watch(['server', 'shared/**/*.ts', 'node_modules', AUTHELIA_INTERRUPT_FILENAME, CONFIG_FILEPATH], {
persistent: true,
ignoreInitial: true,
});
// Properly cleanup server and client if ctrl-c is hit
process.on('SIGINT', function() {
killServer();
killClient();
fs.unlinkSync(ENVIRONMENT_FILENAME);
process.exit();
});
let serverProcess;
function reloadServer() {
killServer(() => {
startServer();
});
}
function startServer() {
if (fs.existsSync(AUTHELIA_INTERRUPT_FILENAME)) {
console.log('Authelia is interrupted. Consider removing ' + AUTHELIA_INTERRUPT_FILENAME + ' if it\'s not expected.');
return;
}
exec('./node_modules/.bin/tslint', ['-c', 'server/tslint.json', '-p', 'server/tsconfig.json'])
.then(function() {
serverProcess = spawn('./scripts/run-dev-server.sh', [CONFIG_FILEPATH]);
serverProcess.stdout.pipe(process.stdout);
serverProcess.stderr.pipe(process.stderr);
});
}
let clientProcess;
function startClient() {
clientProcess = spawn('npm', ['run', 'start'], {
cwd: './client',
env: {
...process.env,
'BROWSER': 'none'
}
});
clientProcess.stdout.pipe(process.stdout);
clientProcess.stderr.pipe(process.stderr);
}
function killServer(onExit) {
if (serverProcess) {
serverProcess.on('exit', () => {
serverProcess = undefined;
if (onExit) onExit();
});
try {
kill(serverProcess.pid, 'SIGKILL');
} catch (e) {
console.error(e);
if (onExit) onExit();
}
} else {
if (onExit) onExit();
}
}
function killClient(onExit) {
if (clientProcess) {
clientProcess.on('exit', () => {
clientProcess = undefined;
if (onExit) onExit();
});
try {
kill(clientProcess.pid, 'SIGKILL');
} catch (e) {
console.error(e);
if (onExit) onExit();
}
} else {
if (onExit) onExit();
}
}
function generateConfigurationSchema() {
return ixec('./node_modules/.bin/typescript-json-schema',
['-o', 'server/src/lib/configuration/Configuration.schema.json', '--strictNullChecks', '--required', 'server/tsconfig.json', 'Configuration']);
}
function reload(path) {
console.log(`File ${path} has been changed, reloading...`);
if (path.startsWith('server/src/lib/configuration/schema')) {
console.log('Schema needs to be regenerated.');
generateConfigurationSchema();
}
else if (path === AUTHELIA_INTERRUPT_FILENAME) {
if (fs.existsSync(path)) {
console.log('Authelia is being interrupted.');
killServer();
} else {
console.log('Authelia is restarting.');
startServer();
}
return;
}
reloadServer();
}
function exec(command, args) {
return new Promise((resolve, reject) => {
const cmd = spawn(command, args);
cmd.stdout.pipe(process.stdout);
cmd.stderr.pipe(process.stderr);
cmd.on('close', (code) => {
if (code == 0) {
resolve();
return;
}
reject(new Error('Status code ' + code));
});
});
}
async function main() {
console.log(`Create suite file ${ENVIRONMENT_FILENAME}.`);
fs.writeFileSync(ENVIRONMENT_FILENAME, program.suite);
console.log(`Render nginx configuration...`);
await exec('./example/compose/nginx/portal/render.js');
console.log(`Prepare environment with docker-compose...`);
await exec('./scripts/utils/prepare-environment.sh');
console.log('Start watching...');
tsWatcher.on('add', reload);
tsWatcher.on('unlink', reload);
tsWatcher.on('change', reload);
startServer();
startClient();
}
main()

View File

@ -0,0 +1,10 @@
#!/usr/bin/env node
var program = require('commander');
program
.command('start', 'Start a suite.')
.command('list', 'List the available suites')
.command('test', 'Test a suite.')
.command('clean', 'Clean remaining environment artifacts (like docker-compose env).')
.parse(process.argv);

View File

@ -0,0 +1,18 @@
#!/usr/bin/env node
var program = require('commander');
var spawn = require('child_process').spawn;
program
.description(`Clean environment artifacts from previous suites.`)
.parse(process.argv)
runEnvProcess = spawn('./node_modules/.bin/ts-node -P test/tsconfig.json -- ./scripts/clean-environment.ts', {
shell: true
});
runEnvProcess.stdout.pipe(process.stdout);
runEnvProcess.stderr.pipe(process.stderr);
runEnvProcess.on('exit', function(statusCode) {
process.exit(statusCode);
});

View File

@ -0,0 +1,10 @@
#!/usr/bin/env node
var program = require('commander');
var ListSuites = require('./utils/ListSuites');
program
.description(`List the available suites.`)
.parse(process.argv)
console.log(ListSuites().join("\n"));

View File

@ -0,0 +1,53 @@
#!/usr/bin/env node
var program = require('commander');
var spawn = require('child_process').spawn;
var fs = require('fs');
let suite;
program
.description(`Start the suite provided as argument. You can list the suites with the 'list' command.`)
.arguments('<suite>')
.action((suiteArg) => suite = suiteArg)
.parse(process.argv)
if (!suite) {
program.help();
}
const ENVIRONMENT_FILENAME = '.suite';
let runEnvProcess;
// Sometime SIGINT is received twice, we make sure with this variable that we cleanup
// only once.
var alreadyCleaningUp = false;
// Properly cleanup server and client if ctrl-c is hit
process.on('SIGINT', function() {
if (alreadyCleaningUp) return;
alreadyCleaningUp = true;
console.log('Cleanup procedure is running...');
});
async function main() {
fs.writeFileSync(ENVIRONMENT_FILENAME, suite);
// Start the environment from ts-node process.
runEnvProcess = spawn('./node_modules/.bin/ts-node -P test/tsconfig.json -- ./scripts/run-environment.ts ' + suite, {
shell: true
});
runEnvProcess.stdout.pipe(process.stdout);
runEnvProcess.stderr.pipe(process.stderr);
runEnvProcess.on('exit', function(statusCode) {
fs.unlinkSync(ENVIRONMENT_FILENAME);
process.exit(statusCode);
});
}
main().catch((err) => {
console.error(err);
process.exit(1)
});

View File

@ -0,0 +1,61 @@
#!/usr/bin/env node
var program = require('commander');
var spawn = require('child_process').spawn;
var fs = require('fs');
let suite;
program
.description('Run the tests for the current suite or the provided one.')
.arguments('[suite]')
.option('--headless', 'Run in headless mode.')
.action((suiteArg) => suite = suiteArg)
.parse(process.argv);
let args = [];
const ENVIRONMENT_FILENAME = '.suite'; // This file is created by the start command.
if (suite) {
if (fs.existsSync(ENVIRONMENT_FILENAME)) {
console.error('You cannot test a suite while another one is running. If you want to test the current suite, ' +
'do not provide the suite argument. Otherwise, stop the current suite and run the command again.');
process.exit(1);
}
console.log('Suite %s provided. Running test related to this suite against built version of Authelia.', suite);
args.push('test/suites/' + suite + '/test.ts');
}
else if (fs.existsSync(ENVIRONMENT_FILENAME)) {
suite = fs.readFileSync(ENVIRONMENT_FILENAME);
console.log('Suite %s detected from dev env. Running test related to this suite.', suite);
args.push('test/suites/' + suite + '/test.ts');
}
else {
console.log('No suite provided therefore all suites will be tested.');
args.push('test/suites/**/test.ts');
}
mocha = spawn('./node_modules/.bin/mocha', ['--exit', '--colors', '--require', 'ts-node/register', ...args], {
env: {
...process.env,
TS_NODE_PROJECT: 'test/tsconfig.json',
HEADLESS: (program.headless) ? 'y' : 'n',
}
});
mocha.stdout.pipe(process.stdout);
mocha.stderr.pipe(process.stderr);
mocha.on('exit', function(statusCode) {
if (statusCode != 0) {
console.error("The tests failed... Mocha exited with status code " + statusCode);
fs.readFile('/tmp/authelia-server.log', function(err, data) {
if (err) {
console.error(err);
return;
}
console.log(data.toString('utf-8'));
});
}
process.exit(statusCode);
})

View File

@ -1,65 +0,0 @@
#!/usr/bin/env node
var program = require('commander');
var spawn = require('child_process').spawn;
var fs = require('fs');
var execSync = require('child_process').execSync;
program
.option('--headless', 'Run in headless mode.')
.parse(process.argv);
let withServer = false;
let args = [];
const ENVIRONMENT_FILENAME = '.suite'; // This file is created by the start command.
if (fs.existsSync(ENVIRONMENT_FILENAME)) {
const suite = fs.readFileSync(ENVIRONMENT_FILENAME);
console.log('Suite %s detected (dev env running). Running test related to this suite.', suite);
args.push('test/suites/' + suite + '/*.ts');
}
else if (program.args.length > 0) {
console.log('No suite detected. Running selected tests against built server.');
withServer = true;
args = program.args;
// Render the production version of the nginx portal configuration
execSync('./example/compose/nginx/portal/render.js --production');
// Prepare the environment
execSync('./scripts/utils/prepare-environment.sh');
}
else {
console.log('No suite detected but no tests have been selected...');
process.exit(1);
}
mocha = spawn('./node_modules/.bin/mocha', ['--exit', '--colors', '--require', 'ts-node/register', ...args], {
env: {
...process.env,
TS_NODE_PROJECT: 'test/tsconfig.json',
WITH_SERVER: (withServer) ? 'y' : 'n',
HEADLESS: (program.headless) ? 'y' : 'n',
}
});
mocha.stdout.on('data', (data) => {
process.stdout.write(`${data}`);
});
mocha.stderr.on('data', (data) => {
process.stderr.write(`${data}`);
});
mocha.on('exit', function(statusCode) {
if (statusCode != 0) {
console.error("The tests failed... Mocha exited with status code " + statusCode);
fs.readFile('/tmp/authelia-server.log', function(err, data) {
if (err) {
console.error(err);
return;
}
console.log(data.toString('utf-8'));
});
}
process.exit(statusCode);
})

View File

@ -19,7 +19,7 @@ authelia-scripts unittest
authelia-scripts docker build
# Run integration tests
authelia-scripts test --headless test/suites/**/*.ts
authelia-scripts suites test --headless
# Test npm deployment before actual deployment
# ./scripts/npm-deployment-test.sh

View File

@ -0,0 +1,8 @@
var ListSuites = require('./utils/ListSuites');
const suites = ListSuites();
suites.forEach(async (suite: string) => {
var { teardown } = require(`../test/suites/${suite}/environment`);;
teardown().catch((err: Error) => console.error(err));
});

View File

@ -1,14 +0,0 @@
#!/bin/bash
set -e
docker-compose \
-f docker-compose.yml \
-f example/compose/mongo/docker-compose.yml \
-f example/compose/redis/docker-compose.yml \
-f example/compose/nginx/backend/docker-compose.yml \
-f example/compose/nginx/portal/docker-compose.yml \
-f example/compose/smtp/docker-compose.yml \
-f example/compose/httpbin/docker-compose.yml \
-f example/compose/ldap/docker-compose.admin.yml \
-f example/compose/ldap/docker-compose.yml $*

View File

@ -1,6 +0,0 @@
#!/bin/sh
# Starts the server with the provided configuration in $1
# This scripts is called from authelia-scripts.
./node_modules/.bin/ts-node -P ./server/tsconfig.json ./server/src/index.ts $*

View File

@ -0,0 +1,40 @@
const userSuite = process.argv[2];
var { setup, teardown } = require(`../test/suites/${userSuite}/environment`);
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
let teardownInProgress = false;
process.on('SIGINT', function() {
if (teardownInProgress) return;
teardownInProgress = true;
console.log('Tearing down environment...');
return teardown()
.then(() => {
process.exit(0)
})
.catch((err: Error) => {
console.error(err);
process.exit(1);
});
});
function main() {
console.log('Setting up environment...');
return setup()
.then(async () => {
while (true) {
await sleep(10000);
}
})
.catch((err: Error) => {
console.error(err);
process.exit(1);
});
}
main();

View File

@ -0,0 +1,13 @@
const { lstatSync, readdirSync } = require('fs')
const { join } = require('path')
const isDirectory = source => lstatSync(source).isDirectory()
const getDirectories = source =>
readdirSync(source)
.map(name => join(source, name))
.filter(isDirectory)
.map(x => x.split('/').slice(-1)[0])
module.exports = function() {
return getDirectories('test/suites/');
}

View File

@ -1,4 +0,0 @@
#!/bin/bash
./scripts/dc-dev.sh up -d
./scripts/dc-dev.sh kill -s SIGHUP nginx-portal

View File

@ -0,0 +1,26 @@
import fs from 'fs';
import AutheliaServerWithHotReload from "./AutheliaServerWithHotReload";
import AutheliaServerInterface from './AutheliaServerInterface';
import AutheliaServerFromDist from './AutheliaServerFromDist';
class AutheliaServer implements AutheliaServerInterface {
private runnerImpl: AutheliaServerInterface;
constructor(configPath: string) {
if (fs.existsSync('.suite')) {
this.runnerImpl = new AutheliaServerWithHotReload(configPath);
} else {
this.runnerImpl = new AutheliaServerFromDist(configPath, true);
}
}
async start() {
await this.runnerImpl.start();
}
async stop() {
await this.runnerImpl.stop();
}
}
export default AutheliaServer;

View File

@ -0,0 +1,39 @@
import AutheliaServerInterface from './AutheliaServerInterface';
import ChildProcess from 'child_process';
import treeKill = require('tree-kill');
import fs from 'fs';
class AutheliaServerFromDist implements AutheliaServerInterface {
private configPath: string;
private logInFile: boolean;
private serverProcess: ChildProcess.ChildProcess | undefined;
constructor(configPath: string, logInFile: boolean = false) {
this.configPath = configPath;
this.logInFile = logInFile;
}
async start() {
this.serverProcess = ChildProcess.spawn('./scripts/authelia-scripts serve ' + this.configPath, {
shell: true
} as any);
if (this.logInFile) {
var logStream = fs.createWriteStream('/tmp/authelia-server.log', {flags: 'a'});
this.serverProcess.stdout.pipe(logStream);
this.serverProcess.stderr.pipe(logStream);
} else {
this.serverProcess.stdout.pipe(process.stdout);
this.serverProcess.stderr.pipe(process.stderr);
}
this.serverProcess.on('exit', (statusCode) => {
console.log('Authelia server exited with code ' + statusCode);
})
}
async stop() {
if (!this.serverProcess) return;
treeKill(this.serverProcess.pid, 'SIGKILL');
}
}
export default AutheliaServerFromDist;

View File

@ -0,0 +1,7 @@
interface AutheliaServerInterface {
start(): Promise<void>;
stop(): Promise<void>
}
export default AutheliaServerInterface;

View File

@ -0,0 +1,149 @@
import Chokidar from 'chokidar';
import fs from 'fs';
import { exec } from '../utils/exec';
import ChildProcess from 'child_process';
import kill from 'tree-kill';
import AutheliaServerInterface from './AutheliaServerInterface';
class AutheliaServerWithHotReload implements AutheliaServerInterface {
private watcher: Chokidar.FSWatcher;
private configPath: string;
private AUTHELIA_INTERRUPT_FILENAME = '.authelia-interrupt';
private serverProcess: ChildProcess.ChildProcess | undefined;
private clientProcess: ChildProcess.ChildProcess | undefined;
constructor(configPath: string) {
this.configPath = configPath;
this.watcher = Chokidar.watch(['server', 'shared/**/*.ts', 'node_modules',
this.AUTHELIA_INTERRUPT_FILENAME, configPath], {
persistent: true,
ignoreInitial: true,
});
}
private async startServer() {
await exec('./node_modules/.bin/tslint -c server/tslint.json -p server/tsconfig.json')
this.serverProcess = ChildProcess.spawn('./node_modules/.bin/ts-node',
['-P', './server/tsconfig.json', './server/src/index.ts', this.configPath], {
env: {...process.env},
});
this.serverProcess.stdout.pipe(process.stdout);
this.serverProcess.stderr.pipe(process.stderr);
this.serverProcess.on('exit', () => {
console.log('Authelia server exited.');
if (!this.serverProcess) return;
this.serverProcess.removeAllListeners();
this.serverProcess = undefined;
});
}
private killServer() {
return new Promise((resolve, reject) => {
if (this.serverProcess) {
try {
const timeout = setTimeout(
() => reject(new Error('Server not killed after timeout.')), 10000);
this.serverProcess.on('exit', () => {
clearTimeout(timeout);
resolve();
});
kill(this.serverProcess.pid, 'SIGKILL');
} catch (e) {
reject(e);
}
} else {
resolve();
}
});
}
private async startClient() {
this.clientProcess = ChildProcess.spawn('npm', ['run', 'start'], {
cwd: './client',
env: {
...process.env,
'BROWSER': 'none'
}
});
this.clientProcess.stdout.pipe(process.stdout);
this.clientProcess.stderr.pipe(process.stderr);
this.clientProcess.on('exit', () => {
console.log('Authelia client exited.');
if (!this.clientProcess) return;
this.clientProcess.removeAllListeners();
this.clientProcess = undefined;
})
}
private killClient() {
return new Promise((resolve, reject) => {
if (this.clientProcess) {
try {
const timeout = setTimeout(
() => reject(new Error('Server not killed after timeout.')), 10000);
this.clientProcess.on('exit', () => {
clearTimeout(timeout);
resolve();
});
kill(this.clientProcess.pid, 'SIGKILL');
} catch (e) {
reject(e);
}
} else {
resolve();
}
});
}
private async generateConfigurationSchema() {
await exec('./node_modules/.bin/typescript-json-schema -o ' +
'server/src/lib/configuration/Configuration.schema.json ' +
'--strictNullChecks --required server/tsconfig.json Configuration');
}
/**
* Handle file changes.
* @param path The path of the file that has been changed.
*/
private async onFileChanged(path: string) {
console.log(`File ${path} has been changed, reloading...`);
if (path.startsWith('server/src/lib/configuration/schema')) {
console.log('Schema needs to be regenerated.');
await this.generateConfigurationSchema();
}
else if (path === this.AUTHELIA_INTERRUPT_FILENAME) {
if (fs.existsSync(path)) {
console.log('Authelia is being interrupted.');
await this.killServer();
} else {
console.log('Authelia is restarting.');
await this.startServer();
}
return;
}
await this.killServer();
await this.startServer();
}
async start() {
if (fs.existsSync(this.AUTHELIA_INTERRUPT_FILENAME)) {
console.log('Authelia is interrupted. Consider removing ' + this.AUTHELIA_INTERRUPT_FILENAME + ' if it\'s not expected.');
return;
}
console.log('Start watching file changes...');
this.watcher.on('add', (p) => this.onFileChanged(p));
this.watcher.on('unlink', (p) => this.onFileChanged(p));
this.watcher.on('change', (p) => this.onFileChanged(p));
this.startClient();
this.startServer();
}
async stop() {
await this.killClient();
await this.killServer();
}
}
export default AutheliaServerWithHotReload;

View File

@ -1,16 +1,17 @@
import WithAutheliaRunning from "./WithAutheliaRunning";
import WithEnvironment from "./WithEnvironment";
import fs from 'fs';
interface AutheliaSuiteType {
(description: string, configPath: string, cb: (this: Mocha.ISuiteCallbackContext) => void): Mocha.ISuite;
only: (description: string, configPath: string, cb: (this: Mocha.ISuiteCallbackContext) => void) => Mocha.ISuite;
}
function AutheliaSuiteBase(description: string, configPath: string,
function AutheliaSuiteBase(description: string, suite: string,
cb: (this: Mocha.ISuiteCallbackContext) => void,
context: (description: string, ctx: (this: Mocha.ISuiteCallbackContext) => void) => Mocha.ISuite) {
return context('Suite: ' + description, function(this: Mocha.ISuiteCallbackContext) {
if (process.env['WITH_SERVER'] == 'y') {
WithAutheliaRunning(configPath);
if (!fs.existsSync('.suite')) {
WithEnvironment(suite);
}
cb.call(this);

View File

@ -0,0 +1,23 @@
import { exec } from '../../helpers/utils/exec';
class DockerCompose {
private commandPrefix: string;
constructor(composeFiles: string[]) {
this.commandPrefix = 'docker-compose ' + composeFiles.map((f) => '-f ' + f).join(' ');
}
async up() {
await exec(this.commandPrefix + ' up -d');
}
async down() {
await exec(this.commandPrefix + ' down');
}
async restart(service: string) {
await exec(this.commandPrefix + ' restart ' + service);
}
}
export default DockerCompose;

View File

@ -0,0 +1,19 @@
import DockerCompose from "./DockerCompose";
class DockerEnvironment {
private dockerCompose: DockerCompose;
constructor(composeFiles: string[]) {
this.dockerCompose = new DockerCompose(composeFiles);
}
async start() {
await this.dockerCompose.up();
}
async stop() {
await this.dockerCompose.down();
}
}
export default DockerEnvironment;

View File

@ -1,44 +0,0 @@
import ChildProcess from 'child_process';
import fs from 'fs';
export default function WithAutheliaRunning(configPath: string, waitTimeout: number = 5000) {
before(function() {
this.timeout(10000);
console.log('Spawning Authelia server with configuration %s.', configPath);
const authelia = ChildProcess.spawn(
'./scripts/authelia-scripts',
['serve', '--no-watch', '--config', configPath],
{detached: true});
authelia.on('exit', function(statusCode) {
console.log('Server terminated with status ' + statusCode);
if (statusCode != 0) {
fs.readFile('/tmp/authelia-server.log', function(err, data) {
if (err) {
console.error(err);
return;
}
console.log(data.toString('utf-8'));
})
}
});
this.authelia = authelia;
const waitPromise = new Promise((resolve, reject) => setTimeout(() => resolve(), waitTimeout));
return waitPromise;
});
after(function() {
this.timeout(10000);
console.log('Killing Authelia server.');
// Kill the group of processes.
process.kill(-this.authelia.pid);
// Leave 5 seconds for the process to terminate.
const waitPromise = new Promise((resolve, reject) => setTimeout(() => resolve(), 5000));
return waitPromise;
});
}

View File

@ -0,0 +1,23 @@
import sleep from '../utils/sleep';
export default function WithAutheliaRunning(suitePath: string, waitTimeout: number = 5000) {
const suite = suitePath.split('/').slice(-1)[0];
var { setup, teardown } = require(`../../suites/${suite}/environment`);
before(async function() {
this.timeout(10000);
console.log('Preparing environment...');
await setup();
await sleep(waitTimeout);
});
after(async function() {
this.timeout(10000);
console.log('Stopping environment...');
await teardown();
await sleep(waitTimeout);
});
}

View File

@ -0,0 +1,20 @@
var spawn = require('child_process').spawn;
export function exec(command: string): Promise<void> {
return new Promise((resolve, reject) => {
const cmd = spawn(command, {
shell: true,
});
cmd.stdout.pipe(process.stdout);
cmd.stderr.pipe(process.stderr);
cmd.on('exit', (statusCode: number) => {
if (statusCode == 0) {
resolve();
return;
}
reject(new Error('Exited with status code ' + statusCode));
});
});
}

View File

@ -0,0 +1,5 @@
export default function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@ -0,0 +1,40 @@
import DockerEnvironment from "../../helpers/context/DockerEnvironment";
import AutheliaServer from "../../helpers/context/AutheliaServer";
import { exec } from "../../helpers/utils/exec";
import fs from 'fs';
const composeFiles = [
'docker-compose.yml',
'example/compose/mongo/docker-compose.yml',
'example/compose/redis/docker-compose.yml',
'example/compose/nginx/backend/docker-compose.yml',
'example/compose/nginx/portal/docker-compose.yml',
'example/compose/smtp/docker-compose.yml',
'example/compose/httpbin/docker-compose.yml',
'example/compose/ldap/docker-compose.admin.yml', // This is just used for administration, not for testing.
'example/compose/ldap/docker-compose.yml'
]
const dockerEnv = new DockerEnvironment(composeFiles);
const autheliaServer = new AutheliaServer(__dirname + '/config.yml');
async function setup() {
// In dev mode Authelia has the server served on one port and the frontend on another port.
await exec('./example/compose/nginx/portal/render.js ' + (fs.existsSync('.suite') ? '': '--production'));
console.log(`Prepare environment with docker-compose...`);
await dockerEnv.start();
console.log('Start Authelia server.');
await autheliaServer.start();
}
async function teardown() {
console.log('Stop Authelia server.');
await autheliaServer.stop();
console.log(`Cleanup environment with docker-compose...`);
await dockerEnv.stop();
}
export { setup, teardown, composeFiles };

View File

@ -3,6 +3,8 @@ import FullLogin from "../../../helpers/FullLogin";
import child_process from 'child_process';
import WithDriver from "../../../helpers/context/WithDriver";
import Logout from "../../../helpers/Logout";
import { composeFiles } from '../environment';
import DockerCompose from "../../../helpers/context/DockerCompose";
export default function() {
after(async function() {
@ -15,7 +17,8 @@ export default function() {
this.timeout(30000);
const secret = await LoginAndRegisterTotp(this.driver, "john", "password", true);
child_process.execSync("./scripts/dc-dev.sh restart mongo");
const dockerCompose = new DockerCompose(composeFiles);
await dockerCompose.restart('mongo');
await FullLogin(this.driver, "john", secret, "https://admin.example.com:8080/secret.html");
});
}

View File

@ -8,11 +8,7 @@ import BasicAuthentication from "./scenarii/BasicAuthentication";
import AutheliaRestart from "./scenarii/AutheliaRestart";
import AuthenticationRegulation from "./scenarii/AuthenticationRegulation";
before(function() {
});
AutheliaSuite('Complete configuration', __dirname + '/config.yml', function() {
AutheliaSuite('Complete configuration', __dirname, function() {
this.timeout(10000);
describe('Custom headers forwarded to backend', CustomHeadersForwarded);

View File

@ -0,0 +1,18 @@
import { exec } from '../../helpers/utils/exec';
import ChildProcess from 'child_process';
async function setup() {
await exec('docker run -d -v $(pwd)/config.yml:/etc/authelia/config.yml --name authelia-test clems4ever/authelia > /dev/null');
console.log('Container has been spawned.');
}
async function teardown() {
try {
ChildProcess.execSync('docker ps | grep "authelia-test"');
await exec('docker rm -f authelia-test > /dev/null');
} catch (e) {
// If grep does not find anything, execSync throws an exception since the command returns 1.
}
}
export { setup, teardown };

View File

@ -1,27 +0,0 @@
import ChildProcess from 'child_process';
import Bluebird from 'bluebird';
import Assert from 'assert';
const execAsync = Bluebird.promisify<string, string>(ChildProcess.exec);
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
describe('Test docker container can run', function() {
this.timeout(15000);
before(async function() {
await execAsync('docker run -d -v $(pwd)/config.yml:/etc/authelia/config.yml --name authelia-test clems4ever/authelia');
});
after(async function() {
await execAsync('docker rm -f authelia-test');
});
it('should be running', async function() {
await sleep(5000);
const output: string = await execAsync('docker ps -a | grep "authelia-test"');
Assert(output.match(new RegExp('Up [0-9] seconds')));
});
});

View File

@ -0,0 +1,17 @@
import ChildProcess from 'child_process';
import Bluebird from 'bluebird';
import Assert from 'assert';
import sleep from '../../helpers/utils/sleep';
import AutheliaSuite from '../../helpers/context/AutheliaSuite';
const execAsync = Bluebird.promisify<string, string>(ChildProcess.exec);
AutheliaSuite('Test docker container runs as expected', __dirname, function() {
this.timeout(15000);
it('should be running', async function() {
await sleep(5000);
const output: string = await execAsync('docker ps -a | grep "authelia-test"');
Assert(output.match(new RegExp('Up [0-9]+ seconds')));
});
});

View File

@ -0,0 +1,27 @@
import fs from 'fs';
import { exec } from "../../helpers/utils/exec";
import AutheliaServer from "../../helpers/context/AutheliaServer";
import DockerEnvironment from "../../helpers/context/DockerEnvironment";
const autheliaServer = new AutheliaServer(__dirname + '/config.yml');
const dockerEnv = new DockerEnvironment([
'docker-compose.yml',
'example/compose/nginx/backend/docker-compose.yml',
'example/compose/nginx/portal/docker-compose.yml',
'example/compose/smtp/docker-compose.yml',
])
async function setup() {
await exec('mkdir -p /tmp/authelia/db');
await exec('./example/compose/nginx/portal/render.js ' + (fs.existsSync('.suite') ? '': '--production'));
await dockerEnv.start();
await autheliaServer.start();
}
async function teardown() {
await dockerEnv.stop();
await autheliaServer.stop();
await exec('mkdir -p /tmp/authelia/db');
}
export { setup, teardown };

View File

@ -1,6 +1,3 @@
import ChildProcess from 'child_process';
import Bluebird from "bluebird";
import AutheliaSuite from "../../helpers/context/AutheliaSuite";
import BadPassword from "./scenarii/BadPassword";
import RegisterTotp from './scenarii/RegisterTotp';
@ -12,21 +9,12 @@ import VerifyEndpoint from './scenarii/VerifyEndpoint';
import RequiredTwoFactor from './scenarii/RequiredTwoFactor';
import LogoutRedirectToAlreadyLoggedIn from './scenarii/LogoutRedirectToAlreadyLoggedIn';
import SimpleAuthentication from './scenarii/SimpleAuthentication';
import { exec } from '../../helpers/utils/exec';
const execAsync = Bluebird.promisify(ChildProcess.exec);
before(async function() {
await execAsync('mkdir -p /tmp/authelia/db');
});
after(async function() {
await execAsync('rm -r /tmp/authelia/db');
});
AutheliaSuite('Minimal configuration', __dirname + '/config.yml', function() {
AutheliaSuite('Minimal configuration', __dirname, function() {
this.timeout(10000);
beforeEach(async function() {
await execAsync('cp users_database.example.yml users_database.yml');
await exec('cp users_database.example.yml users_database.yml');
});
describe('Simple authentication', SimpleAuthentication);

View File

@ -10,7 +10,7 @@
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"isolatedModules": false,
"noEmit": true,
"lib": [
"esnext"