mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
d9e487c99f
Displaying only one option at 2FA stage will allow to add more options like DUO push or OAuth. The user can switch to other option and in this case the option is remembered so that next time, the user will see the same option. The latest option is considered as the prefered option by Authelia.
186 lines
5.8 KiB
TypeScript
186 lines
5.8 KiB
TypeScript
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';
|
|
import sleep from '../utils/sleep';
|
|
|
|
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;
|
|
private filesChangedBuffer: string[] = [];
|
|
private changeInProgress: boolean = false;
|
|
|
|
constructor(configPath: string, watchedPaths: string[]) {
|
|
this.configPath = configPath;
|
|
this.watcher = Chokidar.watch(['server', 'shared/**/*.ts', 'node_modules',
|
|
this.AUTHELIA_INTERRUPT_FILENAME, configPath].concat(watchedPaths), {
|
|
persistent: true,
|
|
ignoreInitial: true,
|
|
});
|
|
}
|
|
|
|
private async startServer() {
|
|
if (this.serverProcess) return;
|
|
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', () => {
|
|
if (!this.serverProcess) return;
|
|
console.log('Authelia server with pid=%s exited.', this.serverProcess.pid);
|
|
this.serverProcess.removeAllListeners();
|
|
this.serverProcess = undefined;
|
|
});
|
|
}
|
|
|
|
private killServer() {
|
|
return new Promise((resolve, reject) => {
|
|
if (this.serverProcess) {
|
|
const pid = this.serverProcess.pid;
|
|
try {
|
|
const timeout = setTimeout(
|
|
() => reject(new Error(`Server with pid=${pid} 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() {
|
|
if (this.clientProcess) return;
|
|
|
|
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', () => {
|
|
if (!this.clientProcess) return;
|
|
console.log('Authelia client exited with pid=%s.', this.clientProcess.pid);
|
|
this.clientProcess.removeAllListeners();
|
|
this.clientProcess = undefined;
|
|
})
|
|
}
|
|
|
|
private killClient() {
|
|
return new Promise((resolve, reject) => {
|
|
if (this.clientProcess) {
|
|
const pid = this.clientProcess.pid;
|
|
try {
|
|
const timeout = setTimeout(
|
|
() => reject(new Error(`Server with pid=${pid} 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 onFilesChanged = async (paths: string[]) => {
|
|
const containsSchemaFiles = paths.filter(
|
|
(p) => p.startsWith('server/src/lib/configuration/schema')).length > 0;
|
|
if (containsSchemaFiles) {
|
|
console.log('Schema needs to be regenerated.');
|
|
await this.generateConfigurationSchema();
|
|
}
|
|
|
|
const interruptFile = paths.filter(
|
|
(p) => p === this.AUTHELIA_INTERRUPT_FILENAME).length > 0;
|
|
if (interruptFile) {
|
|
if (fs.existsSync(this.AUTHELIA_INTERRUPT_FILENAME)) {
|
|
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();
|
|
|
|
if (this.filesChangedBuffer.length > 0) {
|
|
await this.consumeFileChanged();
|
|
}
|
|
}
|
|
|
|
private async consumeFileChanged() {
|
|
this.changeInProgress = true;
|
|
const paths = this.filesChangedBuffer;
|
|
this.filesChangedBuffer = [];
|
|
try {
|
|
await this.onFilesChanged(paths);
|
|
} catch(e) {
|
|
console.error(e);
|
|
}
|
|
this.changeInProgress = false;
|
|
}
|
|
|
|
private enqueueFileChanged(path: string) {
|
|
console.log(`File ${path} has been changed, reloading...`);
|
|
this.filesChangedBuffer.push(path);
|
|
if (this.changeInProgress) return;
|
|
this.consumeFileChanged();
|
|
}
|
|
|
|
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.enqueueFileChanged(p));
|
|
this.watcher.on('unlink', (p) => this.enqueueFileChanged(p));
|
|
this.watcher.on('change', (p) => this.enqueueFileChanged(p));
|
|
|
|
this.startClient();
|
|
this.startServer();
|
|
}
|
|
|
|
async stop() {
|
|
await this.killClient();
|
|
await this.killServer();
|
|
await sleep(2000);
|
|
}
|
|
}
|
|
|
|
export default AutheliaServerWithHotReload; |