diff --git a/Gruntfile.js b/Gruntfile.js index cf7c78a1..413b9684 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,6 +15,11 @@ module.exports = function (grunt) { cmd: "./node_modules/.bin/tsc", args: ['-p', 'server/tsconfig.json'] }, + "generate-config-schema": { + cmd: "./node_modules/.bin/typescript-json-schema", + args: ["-o", `${buildDir}/server/src/lib/configuration/Configuration.schema.json`, "--strictNullChecks", + "--required", "server/tsconfig.json", "UserConfiguration"] + }, "compile-client": { cmd: "./node_modules/.bin/tsc", args: ['-p', 'client/tsconfig.json'] @@ -178,7 +183,7 @@ module.exports = function (grunt) { grunt.registerTask('copy-resources', ['copy:resources', 'copy:views', 'copy:images', 'copy:thirdparties', 'concat:css']); grunt.registerTask('build-client', ['compile-client', 'browserify']); - grunt.registerTask('build-server', ['compile-server', 'copy-resources']); + grunt.registerTask('build-server', ['compile-server', 'copy-resources', 'run:generate-config-schema']); grunt.registerTask('build', ['build-client', 'build-server']); grunt.registerTask('build-dist', ['build', 'run:minify', 'cssmin', 'run:include-minified-script']); diff --git a/package.json b/package.json index 5a3e0a06..c30331f8 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "title": "Authelia API documentation" }, "dependencies": { + "ajv": "^5.2.3", "bluebird": "^3.4.7", "body-parser": "^1.15.2", "connect-redis": "^3.3.0", @@ -106,6 +107,7 @@ "ts-node": "^3.3.0", "tslint": "^5.2.0", "typescript": "^2.3.2", + "typescript-json-schema": "^0.17.0", "u2f-api": "0.0.9", "uglify-es": "^3.0.15" }, diff --git a/server/src/lib/configuration/Configuration.schema.json b/server/src/lib/configuration/Configuration.schema.json new file mode 100644 index 00000000..ccb76813 --- /dev/null +++ b/server/src/lib/configuration/Configuration.schema.json @@ -0,0 +1,374 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "ACLConfiguration": { + "properties": { + "any": { + "items": { + "properties": { + "domain": { + "type": "string" + }, + "policy": { + "$ref": "#/definitions/ACLPolicy" + }, + "resources": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "domain", + "policy" + ], + "type": "object" + }, + "type": "array" + }, + "default_policy": { + "$ref": "#/definitions/ACLPolicy" + }, + "groups": { + "additionalProperties": { + "items": { + "properties": { + "domain": { + "type": "string" + }, + "policy": { + "$ref": "#/definitions/ACLPolicy" + }, + "resources": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "domain", + "policy" + ], + "type": "object" + }, + "type": "array" + }, + "type": "object" + }, + "users": { + "additionalProperties": { + "items": { + "properties": { + "domain": { + "type": "string" + }, + "policy": { + "$ref": "#/definitions/ACLPolicy" + }, + "resources": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "domain", + "policy" + ], + "type": "object" + }, + "type": "array" + }, + "type": "object" + } + }, + "required": [ + "any", + "default_policy", + "groups", + "users" + ], + "type": "object" + }, + "ACLPolicy": { + "enum": [ + "allow", + "deny" + ], + "type": "string" + }, + "AuthenticationMethod": { + "enum": [ + "basic_auth", + "two_factor" + ], + "type": "string" + }, + "AuthenticationMethodsConfiguration": { + "properties": { + "default_method": { + "$ref": "#/definitions/AuthenticationMethod" + }, + "per_subdomain_methods": { + "additionalProperties": { + "enum": [ + "basic_auth", + "two_factor" + ], + "type": "string" + }, + "type": "object" + } + }, + "required": [ + "default_method", + "per_subdomain_methods" + ], + "type": "object" + }, + "FileSystemNotifierConfiguration": { + "properties": { + "filename": { + "type": "string" + } + }, + "required": [ + "filename" + ], + "type": "object" + }, + "GmailNotifierConfiguration": { + "properties": { + "password": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "password", + "sender", + "username" + ], + "type": "object" + }, + "LocalStorageConfiguration": { + "properties": { + "in_memory": { + "type": "boolean" + }, + "path": { + "type": "string" + } + }, + "type": "object" + }, + "MongoStorageConfiguration": { + "properties": { + "url": { + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" + }, + "NotifierConfiguration": { + "properties": { + "filesystem": { + "$ref": "#/definitions/FileSystemNotifierConfiguration" + }, + "gmail": { + "$ref": "#/definitions/GmailNotifierConfiguration" + }, + "smtp": { + "$ref": "#/definitions/SmtpNotifierConfiguration" + } + }, + "type": "object" + }, + "RegulationConfiguration": { + "properties": { + "ban_time": { + "type": "number" + }, + "find_time": { + "type": "number" + }, + "max_retries": { + "type": "number" + } + }, + "required": [ + "ban_time", + "find_time", + "max_retries" + ], + "type": "object" + }, + "SessionCookieConfiguration": { + "properties": { + "domain": { + "type": "string" + }, + "expiration": { + "type": "number" + }, + "redis": { + "$ref": "#/definitions/SessionRedisOptions" + }, + "secret": { + "type": "string" + } + }, + "required": [ + "secret" + ], + "type": "object" + }, + "SessionRedisOptions": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "number" + } + }, + "required": [ + "host", + "port" + ], + "type": "object" + }, + "SmtpNotifierConfiguration": { + "properties": { + "host": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "number" + }, + "secure": { + "type": "boolean" + }, + "sender": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "host", + "password", + "port", + "secure", + "sender", + "username" + ], + "type": "object" + }, + "StorageConfiguration": { + "properties": { + "local": { + "$ref": "#/definitions/LocalStorageConfiguration" + }, + "mongo": { + "$ref": "#/definitions/MongoStorageConfiguration" + } + }, + "type": "object" + }, + "UserLdapConfiguration": { + "properties": { + "additional_groups_dn": { + "type": "string" + }, + "additional_users_dn": { + "type": "string" + }, + "base_dn": { + "type": "string" + }, + "group_name_attribute": { + "type": "string" + }, + "groups_filter": { + "type": "string" + }, + "mail_attribute": { + "type": "string" + }, + "password": { + "type": "string" + }, + "url": { + "type": "string" + }, + "user": { + "type": "string" + }, + "users_filter": { + "type": "string" + } + }, + "required": [ + "base_dn", + "password", + "url", + "user" + ], + "type": "object" + } + }, + "properties": { + "access_control": { + "$ref": "#/definitions/ACLConfiguration" + }, + "authentication_methods": { + "$ref": "#/definitions/AuthenticationMethodsConfiguration" + }, + "ldap": { + "$ref": "#/definitions/UserLdapConfiguration" + }, + "logs_level": { + "type": "string" + }, + "notifier": { + "$ref": "#/definitions/NotifierConfiguration" + }, + "port": { + "type": "number" + }, + "regulation": { + "$ref": "#/definitions/RegulationConfiguration" + }, + "session": { + "$ref": "#/definitions/SessionCookieConfiguration" + }, + "storage": { + "$ref": "#/definitions/StorageConfiguration" + } + }, + "required": [ + "ldap", + "notifier", + "regulation", + "session", + "storage" + ], + "type": "object" +} + diff --git a/server/src/lib/configuration/ConfigurationAdapter.ts b/server/src/lib/configuration/ConfigurationAdapter.ts index da5807bd..bf7365fd 100644 --- a/server/src/lib/configuration/ConfigurationAdapter.ts +++ b/server/src/lib/configuration/ConfigurationAdapter.ts @@ -9,6 +9,7 @@ import { import Util = require("util"); import { ACLAdapter } from "./adapters/ACLAdapter"; import { AuthenticationMethodsAdapter } from "./adapters/AuthenticationMethodsAdapter"; +import { Validator } from "./Validator"; const LDAP_URL_ENV_VARIABLE = "LDAP_URL"; @@ -58,9 +59,8 @@ function adaptLdapConfiguration(userConfig: UserLdapConfiguration): LdapConfigur function adaptFromUserConfiguration(userConfiguration: UserConfiguration) : AppConfiguration { - ensure_key_existence(userConfiguration, "ldap"); - ensure_key_existence(userConfiguration, "session.secret"); - ensure_key_existence(userConfiguration, "regulation"); + if (!Validator.isValid(userConfiguration)) + throw new Error("Configuration is malformed. Please double check your configuration file."); const port = userConfiguration.port || 8080; const ldapConfiguration = adaptLdapConfiguration(userConfiguration.ldap); diff --git a/server/src/lib/configuration/Validator.ts b/server/src/lib/configuration/Validator.ts new file mode 100644 index 00000000..ce88f29a --- /dev/null +++ b/server/src/lib/configuration/Validator.ts @@ -0,0 +1,20 @@ +import Ajv = require("ajv"); +import Path = require("path"); + +export class Validator { + static isValid(configuration: any) { + const schema = require(Path.resolve(__dirname, "./Configuration.schema.json")); + const ajv = new Ajv({ + allErrors: true, + missingRefs: "fail" + }); + ajv.addMetaSchema(require("ajv/lib/refs/json-schema-draft-04.json")); + const valid = ajv.validate(schema, configuration); + if (!valid) { + for (const i in ajv.errors) { + console.log(ajv.errorsText([ajv.errors[i]])); + } + } + return valid; + } +} \ No newline at end of file diff --git a/server/test/configuration/Validator.test.ts b/server/test/configuration/Validator.test.ts new file mode 100644 index 00000000..db433f3d --- /dev/null +++ b/server/test/configuration/Validator.test.ts @@ -0,0 +1,10 @@ +import { Validator } from "../../src/lib/configuration/Validator"; +import Assert = require("assert"); + +describe.only("test validator", function() { + it("should validate a correct user configuration", function() { + Assert(Validator.validate({ + ldap: {} + })); + }); +}); \ No newline at end of file