Merge pull request #131 from clems4ever/disable-second-factor
Allow basic authentication in configurationpull/140/head
commit
d5035b8704
|
@ -53,7 +53,7 @@ module.exports = function (grunt) {
|
||||||
},
|
},
|
||||||
"include-minified-script": {
|
"include-minified-script": {
|
||||||
cmd: "sed",
|
cmd: "sed",
|
||||||
args: ["-i", "s/authelia\.min/authelia/", `${buildDir}/server/src/views/layout/layout.pug`]
|
args: ["-i", "s/authelia\.js/authelia.min.js/", `${buildDir}/server/src/views/layout/layout.pug`]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
copy: {
|
copy: {
|
||||||
|
|
|
@ -22,7 +22,8 @@ used in production to secure internal services in a small docker swarm cluster.
|
||||||
3. [Second factor with U2F security keys](#second-factor-with-u2f-security-keys)
|
3. [Second factor with U2F security keys](#second-factor-with-u2f-security-keys)
|
||||||
4. [Password reset](#password-reset)
|
4. [Password reset](#password-reset)
|
||||||
5. [Access control](#access-control)
|
5. [Access control](#access-control)
|
||||||
6. [Session management with Redis](#session-management-with-redis)
|
6. [Basic authentication](#basic-authentication)
|
||||||
|
7. [Session management with Redis](#session-management-with-redis)
|
||||||
4. [Documentation](#documentation)
|
4. [Documentation](#documentation)
|
||||||
1. [Authelia configuration](#authelia-configuration)
|
1. [Authelia configuration](#authelia-configuration)
|
||||||
1. [API documentation](#api-documentation)
|
1. [API documentation](#api-documentation)
|
||||||
|
@ -37,6 +38,7 @@ used in production to secure internal services in a small docker swarm cluster.
|
||||||
as 2nd factor.
|
as 2nd factor.
|
||||||
* Password reset with identity verification by sending links to user email
|
* Password reset with identity verification by sending links to user email
|
||||||
address.
|
address.
|
||||||
|
* Two-factor and basic authentication methods available.
|
||||||
* Access restriction after too many authentication attempts.
|
* Access restriction after too many authentication attempts.
|
||||||
* Session management using Redis key/value store.
|
* Session management using Redis key/value store.
|
||||||
* User-defined access control per subdomain and resource.
|
* User-defined access control per subdomain and resource.
|
||||||
|
@ -187,6 +189,11 @@ user access to some resources and subdomains. Those rules are defined and fully
|
||||||
in the configuration file. They can apply to users, groups or everyone.
|
in the configuration file. They can apply to users, groups or everyone.
|
||||||
Check out [config.template.yml] to see how they are defined.
|
Check out [config.template.yml] to see how they are defined.
|
||||||
|
|
||||||
|
### Basic Authentication
|
||||||
|
Authelia allows you to customize the authentication method to use for each sub-domain.
|
||||||
|
The supported methods are either "basic_auth" and "two_factor".
|
||||||
|
Please see [config.template.yml] to see an example of configuration.
|
||||||
|
|
||||||
### Session management with Redis
|
### Session management with Redis
|
||||||
When your users authenticate against Authelia, sessions are stored in a Redis key/value store. You can specify your own Redis instance in [config.template.yml].
|
When your users authenticate against Authelia, sessions are stored in a Redis key/value store. You can specify your own Redis instance in [config.template.yml].
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,19 @@
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import Endpoints = require("../../../../shared/api");
|
import Endpoints = require("../../../../shared/api");
|
||||||
import Constants = require("../../../../shared/constants");
|
import Constants = require("../../../../shared/constants");
|
||||||
|
import Util = require("util");
|
||||||
|
|
||||||
export function validate(username: string, password: string,
|
export function validate(username: string, password: string,
|
||||||
redirectUrl: string, onlyBasicAuth: boolean, $: JQueryStatic): BluebirdPromise<string> {
|
redirectUrl: string, $: JQueryStatic): BluebirdPromise<string> {
|
||||||
return new BluebirdPromise<string>(function (resolve, reject) {
|
return new BluebirdPromise<string>(function (resolve, reject) {
|
||||||
const url = Endpoints.FIRST_FACTOR_POST + "?" + Constants.REDIRECT_QUERY_PARAM + "=" + redirectUrl
|
let url: string;
|
||||||
+ "&" + Constants.ONLY_BASIC_AUTH_QUERY_PARAM + "=" + onlyBasicAuth;
|
if (redirectUrl != undefined) {
|
||||||
|
const redirectParam = Util.format("%s=%s", Constants.REDIRECT_QUERY_PARAM, redirectUrl);
|
||||||
|
url = Util.format("%s?%s", Endpoints.FIRST_FACTOR_POST, redirectParam);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
url = Util.format("%s", Endpoints.FIRST_FACTOR_POST);
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
|
@ -17,8 +17,7 @@ export default function (window: Window, $: JQueryStatic,
|
||||||
$(UISelectors.PASSWORD_FIELD_ID).val("");
|
$(UISelectors.PASSWORD_FIELD_ID).val("");
|
||||||
|
|
||||||
const redirectUrl = QueryParametersRetriever.get(Constants.REDIRECT_QUERY_PARAM);
|
const redirectUrl = QueryParametersRetriever.get(Constants.REDIRECT_QUERY_PARAM);
|
||||||
const onlyBasicAuth = QueryParametersRetriever.get(Constants.ONLY_BASIC_AUTH_QUERY_PARAM) ? true : false;
|
firstFactorValidator.validate(username, password, redirectUrl, $)
|
||||||
firstFactorValidator.validate(username, password, redirectUrl, onlyBasicAuth, $)
|
|
||||||
.then(onFirstFactorSuccess, onFirstFactorFailure);
|
.then(onFirstFactorSuccess, onFirstFactorFailure);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ describe("test FirstFactorValidator", function () {
|
||||||
const jqueryMock = JQueryMock.JQueryMock();
|
const jqueryMock = JQueryMock.JQueryMock();
|
||||||
jqueryMock.jquery.ajax.returns(postPromise);
|
jqueryMock.jquery.ajax.returns(postPromise);
|
||||||
|
|
||||||
return FirstFactorValidator.validate("username", "password", "http://redirect", false, jqueryMock.jquery as any);
|
return FirstFactorValidator.validate("username", "password", "http://redirect", jqueryMock.jquery as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
function should_fail_first_factor_validation(errorMessage: string) {
|
function should_fail_first_factor_validation(errorMessage: string) {
|
||||||
|
@ -27,7 +27,7 @@ describe("test FirstFactorValidator", function () {
|
||||||
const jqueryMock = JQueryMock.JQueryMock();
|
const jqueryMock = JQueryMock.JQueryMock();
|
||||||
jqueryMock.jquery.ajax.returns(postPromise);
|
jqueryMock.jquery.ajax.returns(postPromise);
|
||||||
|
|
||||||
return FirstFactorValidator.validate("username", "password", "http://redirect", false, jqueryMock.jquery as any)
|
return FirstFactorValidator.validate("username", "password", "http://redirect", jqueryMock.jquery as any)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return BluebirdPromise.reject(new Error("First factor validation successfully finished while it should have not."));
|
return BluebirdPromise.reject(new Error("First factor validation successfully finished while it should have not."));
|
||||||
}, function (err: Error) {
|
}, function (err: Error) {
|
||||||
|
|
|
@ -47,6 +47,20 @@ ldap:
|
||||||
password: password
|
password: password
|
||||||
|
|
||||||
|
|
||||||
|
# Authentication methods
|
||||||
|
#
|
||||||
|
# Authentication methods can be defined per subdomain.
|
||||||
|
# There are currently two available methods: "basic_auth" and "two_factor"
|
||||||
|
#
|
||||||
|
# Note: by default a domain uses "two_factor" method.
|
||||||
|
#
|
||||||
|
# Note: 'overriden_methods' is a dictionary where keys must be subdomains and
|
||||||
|
# values must be one of the two possible methods.
|
||||||
|
authentication_methods:
|
||||||
|
default_method: two_factor
|
||||||
|
per_subdomain_methods:
|
||||||
|
basicauth.test.local: basic_auth
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -221,7 +221,7 @@ http {
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header Content-Length "";
|
proxy_set_header Content-Length "";
|
||||||
|
|
||||||
proxy_pass http://authelia/verify?only_basic_auth=true;
|
proxy_pass http://authelia/verify;
|
||||||
}
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
@ -236,7 +236,7 @@ http {
|
||||||
auth_request_set $groups $upstream_http_remote_groups;
|
auth_request_set $groups $upstream_http_remote_groups;
|
||||||
proxy_set_header Remote-Groups $groups;
|
proxy_set_header Remote-Groups $groups;
|
||||||
|
|
||||||
error_page 401 =302 https://auth.test.local:8080?redirect=$redirect&only_basic_auth=true;
|
error_page 401 =302 https://auth.test.local:8080?redirect=$redirect;
|
||||||
error_page 403 = https://auth.test.local:8080/error/403;
|
error_page 403 = https://auth.test.local:8080/error/403;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { AuthenticationMethod, AuthenticationMethodsConfiguration } from "./configuration/Configuration";
|
||||||
|
|
||||||
|
export class AuthenticationMethodCalculator {
|
||||||
|
private configuration: AuthenticationMethodsConfiguration;
|
||||||
|
|
||||||
|
constructor(config: AuthenticationMethodsConfiguration) {
|
||||||
|
this.configuration = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
compute(subDomain: string): AuthenticationMethod {
|
||||||
|
if (subDomain in this.configuration.per_subdomain_methods)
|
||||||
|
return this.configuration.per_subdomain_methods[subDomain];
|
||||||
|
return this.configuration.default_method;
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,9 @@ import ResetPasswordRequestPost = require("./routes/password-reset/request/get")
|
||||||
import Error401Get = require("./routes/error/401/get");
|
import Error401Get = require("./routes/error/401/get");
|
||||||
import Error403Get = require("./routes/error/403/get");
|
import Error403Get = require("./routes/error/403/get");
|
||||||
import Error404Get = require("./routes/error/404/get");
|
import Error404Get = require("./routes/error/404/get");
|
||||||
|
|
||||||
|
import LoggedIn = require("./routes/loggedin/get");
|
||||||
|
|
||||||
import { ServerVariablesHandler } from "./ServerVariablesHandler";
|
import { ServerVariablesHandler } from "./ServerVariablesHandler";
|
||||||
|
|
||||||
import Endpoints = require("../../../shared/api");
|
import Endpoints = require("../../../shared/api");
|
||||||
|
@ -72,5 +75,6 @@ export class RestApi {
|
||||||
app.get(Endpoints.ERROR_401_GET, withLog(Error401Get.default));
|
app.get(Endpoints.ERROR_401_GET, withLog(Error401Get.default));
|
||||||
app.get(Endpoints.ERROR_403_GET, withLog(Error403Get.default));
|
app.get(Endpoints.ERROR_403_GET, withLog(Error403Get.default));
|
||||||
app.get(Endpoints.ERROR_404_GET, withLog(Error404Get.default));
|
app.get(Endpoints.ERROR_404_GET, withLog(Error404Get.default));
|
||||||
|
app.get(Endpoints.LOGGED_IN, withLog(LoggedIn.default));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
import U2F = require("u2f");
|
import U2F = require("u2f");
|
||||||
|
|
||||||
import { IRequestLogger } from "./logging/IRequestLogger";
|
import { IRequestLogger } from "./logging/IRequestLogger";
|
||||||
|
@ -14,6 +12,8 @@ import { INotifier } from "./notifiers/INotifier";
|
||||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||||
import Configuration = require("./configuration/Configuration");
|
import Configuration = require("./configuration/Configuration");
|
||||||
import { AccessController } from "./access_control/AccessController";
|
import { AccessController } from "./access_control/AccessController";
|
||||||
|
import { AuthenticationMethodCalculator } from "./AuthenticationMethodCalculator";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface ServerVariables {
|
export interface ServerVariables {
|
||||||
|
@ -29,4 +29,5 @@ export interface ServerVariables {
|
||||||
regulator: AuthenticationRegulator;
|
regulator: AuthenticationRegulator;
|
||||||
config: Configuration.AppConfiguration;
|
config: Configuration.AppConfiguration;
|
||||||
accessController: AccessController;
|
accessController: AccessController;
|
||||||
|
authenticationMethodsCalculator: AuthenticationMethodCalculator;
|
||||||
}
|
}
|
|
@ -33,8 +33,11 @@ import { ICollectionFactory } from "./storage/ICollectionFactory";
|
||||||
import { MongoCollectionFactory } from "./storage/mongo/MongoCollectionFactory";
|
import { MongoCollectionFactory } from "./storage/mongo/MongoCollectionFactory";
|
||||||
import { MongoConnectorFactory } from "./connectors/mongo/MongoConnectorFactory";
|
import { MongoConnectorFactory } from "./connectors/mongo/MongoConnectorFactory";
|
||||||
import { IMongoClient } from "./connectors/mongo/IMongoClient";
|
import { IMongoClient } from "./connectors/mongo/IMongoClient";
|
||||||
|
|
||||||
import { GlobalDependencies } from "../../types/Dependencies";
|
import { GlobalDependencies } from "../../types/Dependencies";
|
||||||
import { ServerVariables } from "./ServerVariables";
|
import { ServerVariables } from "./ServerVariables";
|
||||||
|
import { AuthenticationMethodCalculator } from "./AuthenticationMethodCalculator";
|
||||||
|
|
||||||
|
|
||||||
import express = require("express");
|
import express = require("express");
|
||||||
|
|
||||||
|
@ -78,6 +81,7 @@ export class ServerVariablesHandler {
|
||||||
const accessController = new AccessController(config.access_control, deps.winston);
|
const accessController = new AccessController(config.access_control, deps.winston);
|
||||||
const totpValidator = new TOTPValidator(deps.speakeasy);
|
const totpValidator = new TOTPValidator(deps.speakeasy);
|
||||||
const totpGenerator = new TOTPGenerator(deps.speakeasy);
|
const totpGenerator = new TOTPGenerator(deps.speakeasy);
|
||||||
|
const authenticationMethodCalculator = new AuthenticationMethodCalculator(config.authentication_methods);
|
||||||
|
|
||||||
return UserDataStoreFactory.create(config)
|
return UserDataStoreFactory.create(config)
|
||||||
.then(function (userDataStore: UserDataStore) {
|
.then(function (userDataStore: UserDataStore) {
|
||||||
|
@ -97,6 +101,7 @@ export class ServerVariablesHandler {
|
||||||
totpValidator: totpValidator,
|
totpValidator: totpValidator,
|
||||||
u2f: deps.u2f,
|
u2f: deps.u2f,
|
||||||
userDataStore: userDataStore,
|
userDataStore: userDataStore,
|
||||||
|
authenticationMethodsCalculator: authenticationMethodCalculator
|
||||||
};
|
};
|
||||||
|
|
||||||
app.set(VARIABLES_KEY, variables);
|
app.set(VARIABLES_KEY, variables);
|
||||||
|
@ -150,4 +155,8 @@ export class ServerVariablesHandler {
|
||||||
static getU2F(app: express.Application): typeof U2F {
|
static getU2F(app: express.Application): typeof U2F {
|
||||||
return (app.get(VARIABLES_KEY) as ServerVariables).u2f;
|
return (app.get(VARIABLES_KEY) as ServerVariables).u2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getAuthenticationMethodCalculator(app: express.Application): AuthenticationMethodCalculator {
|
||||||
|
return (app.get(VARIABLES_KEY) as ServerVariables).authenticationMethodsCalculator;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { ACLConfiguration, ACLPolicy, ACLRule } from "../configuration/Configuration";
|
import { ACLConfiguration, ACLPolicy, ACLRule } from "../configuration/Configuration";
|
||||||
import { IAccessController } from "./IAccessController";
|
import { IAccessController } from "./IAccessController";
|
||||||
import { Winston } from "../../../types/Dependencies";
|
import { Winston } from "../../../types/Dependencies";
|
||||||
import { DomainMatcher } from "./DomainMatcher";
|
import { MultipleDomainMatcher } from "./MultipleDomainMatcher";
|
||||||
|
|
||||||
|
|
||||||
enum AccessReturn {
|
enum AccessReturn {
|
||||||
|
@ -17,7 +17,7 @@ function AllowedRule(rule: ACLRule) {
|
||||||
|
|
||||||
function MatchDomain(actualDomain: string) {
|
function MatchDomain(actualDomain: string) {
|
||||||
return function (rule: ACLRule): boolean {
|
return function (rule: ACLRule): boolean {
|
||||||
return DomainMatcher.match(actualDomain, rule.domain);
|
return MultipleDomainMatcher.match(actualDomain, rule.domain);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
export class DomainMatcher {
|
|
||||||
static match(domain: string, allowedDomain: string): boolean {
|
|
||||||
if (allowedDomain.startsWith("*") &&
|
|
||||||
domain.endsWith(allowedDomain.substr(1))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (domain == allowedDomain) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
export class MultipleDomainMatcher {
|
||||||
|
static match(domain: string, pattern: string): boolean {
|
||||||
|
if (pattern.startsWith("*") &&
|
||||||
|
domain.endsWith(pattern.substr(1))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (domain == pattern) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,6 +109,14 @@ export interface RegulationConfiguration {
|
||||||
ban_time: number;
|
ban_time: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare type AuthenticationMethod = 'two_factor' | 'basic_auth';
|
||||||
|
declare type AuthenticationMethodPerSubdomain = { [subdomain: string]: AuthenticationMethod }
|
||||||
|
|
||||||
|
export interface AuthenticationMethodsConfiguration {
|
||||||
|
default_method: AuthenticationMethod;
|
||||||
|
per_subdomain_methods: AuthenticationMethodPerSubdomain;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UserConfiguration {
|
export interface UserConfiguration {
|
||||||
port?: number;
|
port?: number;
|
||||||
logs_level?: string;
|
logs_level?: string;
|
||||||
|
@ -116,6 +124,7 @@ export interface UserConfiguration {
|
||||||
session: SessionCookieConfiguration;
|
session: SessionCookieConfiguration;
|
||||||
storage: StorageConfiguration;
|
storage: StorageConfiguration;
|
||||||
notifier: NotifierConfiguration;
|
notifier: NotifierConfiguration;
|
||||||
|
authentication_methods?: AuthenticationMethodsConfiguration;
|
||||||
access_control?: ACLConfiguration;
|
access_control?: ACLConfiguration;
|
||||||
regulation: RegulationConfiguration;
|
regulation: RegulationConfiguration;
|
||||||
}
|
}
|
||||||
|
@ -127,6 +136,7 @@ export interface AppConfiguration {
|
||||||
session: SessionCookieConfiguration;
|
session: SessionCookieConfiguration;
|
||||||
storage: StorageConfiguration;
|
storage: StorageConfiguration;
|
||||||
notifier: NotifierConfiguration;
|
notifier: NotifierConfiguration;
|
||||||
|
authentication_methods: AuthenticationMethodsConfiguration;
|
||||||
access_control?: ACLConfiguration;
|
access_control?: ACLConfiguration;
|
||||||
regulation: RegulationConfiguration;
|
regulation: RegulationConfiguration;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from "./Configuration";
|
} from "./Configuration";
|
||||||
import Util = require("util");
|
import Util = require("util");
|
||||||
import { ACLAdapter } from "./adapters/ACLAdapter";
|
import { ACLAdapter } from "./adapters/ACLAdapter";
|
||||||
|
import { AuthenticationMethodsAdapter } from "./adapters/AuthenticationMethodsAdapter";
|
||||||
|
|
||||||
const LDAP_URL_ENV_VARIABLE = "LDAP_URL";
|
const LDAP_URL_ENV_VARIABLE = "LDAP_URL";
|
||||||
|
|
||||||
|
@ -55,15 +56,16 @@ function adaptLdapConfiguration(userConfig: UserLdapConfiguration): LdapConfigur
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function adaptFromUserConfiguration(userConfiguration: UserConfiguration): AppConfiguration {
|
function adaptFromUserConfiguration(userConfiguration: UserConfiguration)
|
||||||
|
: AppConfiguration {
|
||||||
ensure_key_existence(userConfiguration, "ldap");
|
ensure_key_existence(userConfiguration, "ldap");
|
||||||
// ensure_key_existence(userConfiguration, "ldap.url");
|
|
||||||
// ensure_key_existence(userConfiguration, "ldap.base_dn");
|
|
||||||
ensure_key_existence(userConfiguration, "session.secret");
|
ensure_key_existence(userConfiguration, "session.secret");
|
||||||
ensure_key_existence(userConfiguration, "regulation");
|
ensure_key_existence(userConfiguration, "regulation");
|
||||||
|
|
||||||
const port = userConfiguration.port || 8080;
|
const port = userConfiguration.port || 8080;
|
||||||
const ldapConfiguration = adaptLdapConfiguration(userConfiguration.ldap);
|
const ldapConfiguration = adaptLdapConfiguration(userConfiguration.ldap);
|
||||||
|
const authenticationMethods = AuthenticationMethodsAdapter
|
||||||
|
.adapt(userConfiguration.authentication_methods);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
port: port,
|
port: port,
|
||||||
|
@ -81,7 +83,8 @@ function adaptFromUserConfiguration(userConfiguration: UserConfiguration): AppCo
|
||||||
logs_level: get_optional<string>(userConfiguration, "logs_level", "info"),
|
logs_level: get_optional<string>(userConfiguration, "logs_level", "info"),
|
||||||
notifier: ObjectPath.get<object, NotifierConfiguration>(userConfiguration, "notifier"),
|
notifier: ObjectPath.get<object, NotifierConfiguration>(userConfiguration, "notifier"),
|
||||||
access_control: ACLAdapter.adapt(userConfiguration.access_control),
|
access_control: ACLAdapter.adapt(userConfiguration.access_control),
|
||||||
regulation: userConfiguration.regulation
|
regulation: userConfiguration.regulation,
|
||||||
|
authentication_methods: authenticationMethods
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import { ACLConfiguration } from "../Configuration";
|
import { ACLConfiguration } from "../Configuration";
|
||||||
|
import { ObjectCloner } from "../../utils/ObjectCloner";
|
||||||
function clone(obj: any): any {
|
|
||||||
return JSON.parse(JSON.stringify(obj));
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_POLICY = "deny";
|
const DEFAULT_POLICY = "deny";
|
||||||
|
|
||||||
|
@ -32,7 +29,7 @@ export class ACLAdapter {
|
||||||
static adapt(configuration: ACLConfiguration): ACLConfiguration {
|
static adapt(configuration: ACLConfiguration): ACLConfiguration {
|
||||||
if (!configuration) return;
|
if (!configuration) return;
|
||||||
|
|
||||||
const newConfiguration: ACLConfiguration = clone(configuration);
|
const newConfiguration: ACLConfiguration = ObjectCloner.clone(configuration);
|
||||||
adaptDefaultPolicy(newConfiguration);
|
adaptDefaultPolicy(newConfiguration);
|
||||||
adaptAny(newConfiguration);
|
adaptAny(newConfiguration);
|
||||||
adaptGroups(newConfiguration);
|
adaptGroups(newConfiguration);
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { AuthenticationMethodsConfiguration } from "../Configuration";
|
||||||
|
import { ObjectCloner } from "../../utils/ObjectCloner";
|
||||||
|
|
||||||
|
function clone(obj: any): any {
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AuthenticationMethodsAdapter {
|
||||||
|
static adapt(authentication_methods: AuthenticationMethodsConfiguration)
|
||||||
|
: AuthenticationMethodsConfiguration {
|
||||||
|
if (!authentication_methods) {
|
||||||
|
return {
|
||||||
|
default_method: "two_factor",
|
||||||
|
per_subdomain_methods: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const newAuthMethods: AuthenticationMethodsConfiguration
|
||||||
|
= ObjectCloner.clone(authentication_methods);
|
||||||
|
|
||||||
|
if (!newAuthMethods.default_method)
|
||||||
|
newAuthMethods.default_method = "two_factor";
|
||||||
|
|
||||||
|
if (!newAuthMethods.per_subdomain_methods ||
|
||||||
|
newAuthMethods.per_subdomain_methods.constructor !== Object)
|
||||||
|
newAuthMethods.per_subdomain_methods = {};
|
||||||
|
|
||||||
|
return newAuthMethods;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,11 +6,52 @@ import Endpoints = require("../../../../../shared/api");
|
||||||
import AuthenticationValidator = require("../../AuthenticationValidator");
|
import AuthenticationValidator = require("../../AuthenticationValidator");
|
||||||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import AuthenticationSession = require("../../AuthenticationSession");
|
||||||
|
import Constants = require("../../../../../shared/constants");
|
||||||
|
import Util = require("util");
|
||||||
|
|
||||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
function getRedirectParam(req: express.Request) {
|
||||||
|
return req.query[Constants.REDIRECT_QUERY_PARAM] != "undefined"
|
||||||
|
? req.query[Constants.REDIRECT_QUERY_PARAM]
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectToSecondFactorPage(req: express.Request, res: express.Response) {
|
||||||
|
const redirectUrl = getRedirectParam(req);
|
||||||
|
if (!redirectUrl)
|
||||||
|
res.redirect(Endpoints.SECOND_FACTOR_GET);
|
||||||
|
else
|
||||||
|
res.redirect(Util.format("%s?redirect=%s", Endpoints.SECOND_FACTOR_GET,
|
||||||
|
encodeURIComponent(redirectUrl)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectToService(req: express.Request, res: express.Response) {
|
||||||
|
const redirectUrl = getRedirectParam(req);
|
||||||
|
if (!redirectUrl)
|
||||||
|
res.redirect(Endpoints.LOGGED_IN);
|
||||||
|
else
|
||||||
|
res.redirect(redirectUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFirstFactor(res: express.Response) {
|
||||||
res.render("firstfactor", {
|
res.render("firstfactor", {
|
||||||
first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
|
first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
|
||||||
reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET
|
reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET
|
||||||
});
|
});
|
||||||
return BluebirdPromise.resolve();
|
}
|
||||||
|
|
||||||
|
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||||
|
return AuthenticationSession.get(req)
|
||||||
|
.then(function (authSession) {
|
||||||
|
if (authSession.first_factor) {
|
||||||
|
if (authSession.second_factor)
|
||||||
|
redirectToService(req, res);
|
||||||
|
else
|
||||||
|
redirectToSecondFactorPage(req, res);
|
||||||
|
return BluebirdPromise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFirstFactor(res);
|
||||||
|
return BluebirdPromise.resolve();
|
||||||
|
});
|
||||||
}
|
}
|
|
@ -11,6 +11,7 @@ import ErrorReplies = require("../../ErrorReplies");
|
||||||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||||
import AuthenticationSession = require("../../AuthenticationSession");
|
import AuthenticationSession = require("../../AuthenticationSession");
|
||||||
import Constants = require("../../../../../shared/constants");
|
import Constants = require("../../../../../shared/constants");
|
||||||
|
import { DomainExtractor } from "../../utils/DomainExtractor";
|
||||||
|
|
||||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||||
const username: string = req.body.username;
|
const username: string = req.body.username;
|
||||||
|
@ -28,6 +29,8 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
||||||
|
|
||||||
const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app);
|
const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app);
|
||||||
const accessController = ServerVariablesHandler.getAccessController(req.app);
|
const accessController = ServerVariablesHandler.getAccessController(req.app);
|
||||||
|
const authenticationMethodsCalculator =
|
||||||
|
ServerVariablesHandler.getAuthenticationMethodCalculator(req.app);
|
||||||
let authSession: AuthenticationSession.AuthenticationSession;
|
let authSession: AuthenticationSession.AuthenticationSession;
|
||||||
|
|
||||||
logger.info(req, "Starting authentication of user \"%s\"", username);
|
logger.info(req, "Starting authentication of user \"%s\"", username);
|
||||||
|
@ -45,11 +48,16 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
||||||
JSON.stringify(groupsAndEmails));
|
JSON.stringify(groupsAndEmails));
|
||||||
authSession.userid = username;
|
authSession.userid = username;
|
||||||
authSession.first_factor = true;
|
authSession.first_factor = true;
|
||||||
const redirectUrl = req.query[Constants.REDIRECT_QUERY_PARAM];
|
const redirectUrl = req.query[Constants.REDIRECT_QUERY_PARAM] !== "undefined"
|
||||||
const onlyBasicAuth = req.query[Constants.ONLY_BASIC_AUTH_QUERY_PARAM] === "true";
|
// Fuck, don't know why it is a string!
|
||||||
|
? req.query[Constants.REDIRECT_QUERY_PARAM]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const emails: string[] = groupsAndEmails.emails;
|
const emails: string[] = groupsAndEmails.emails;
|
||||||
const groups: string[] = groupsAndEmails.groups;
|
const groups: string[] = groupsAndEmails.groups;
|
||||||
|
const redirectHost: string = DomainExtractor.fromUrl(redirectUrl);
|
||||||
|
const authMethod = authenticationMethodsCalculator.compute(redirectHost);
|
||||||
|
logger.debug(req, "Authentication method for \"%s\" is \"%s\"", redirectHost, authMethod);
|
||||||
|
|
||||||
if (!emails || emails.length <= 0) {
|
if (!emails || emails.length <= 0) {
|
||||||
const errMessage = "No emails found. The user should have at least one email address to reset password.";
|
const errMessage = "No emails found. The user should have at least one email address to reset password.";
|
||||||
|
@ -63,22 +71,26 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
||||||
logger.debug(req, "Mark successful authentication to regulator.");
|
logger.debug(req, "Mark successful authentication to regulator.");
|
||||||
regulator.mark(username, true);
|
regulator.mark(username, true);
|
||||||
|
|
||||||
if (onlyBasicAuth) {
|
if (authMethod == "basic_auth") {
|
||||||
res.send({
|
res.send({
|
||||||
redirect: redirectUrl
|
redirect: redirectUrl
|
||||||
});
|
});
|
||||||
logger.debug(req, "Redirect to '%s'", redirectUrl);
|
logger.debug(req, "Redirect to '%s'", redirectUrl);
|
||||||
}
|
}
|
||||||
else {
|
else if (authMethod == "two_factor") {
|
||||||
let newRedirectUrl = Endpoint.SECOND_FACTOR_GET;
|
let newRedirectUrl = Endpoint.SECOND_FACTOR_GET;
|
||||||
if (redirectUrl !== "undefined") {
|
if (redirectUrl) {
|
||||||
newRedirectUrl += "?redirect=" + encodeURIComponent(redirectUrl);
|
newRedirectUrl += "?" + Constants.REDIRECT_QUERY_PARAM + "="
|
||||||
|
+ encodeURIComponent(redirectUrl);
|
||||||
}
|
}
|
||||||
logger.debug(req, "Redirect to '%s'", newRedirectUrl, typeof redirectUrl);
|
logger.debug(req, "Redirect to '%s'", newRedirectUrl, typeof redirectUrl);
|
||||||
res.send({
|
res.send({
|
||||||
redirect: newRedirectUrl
|
redirect: newRedirectUrl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
return BluebirdPromise.reject(new Error("Unknown authentication method for this domain."));
|
||||||
|
}
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(req, res, logger))
|
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(req, res, logger))
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import Express = require("express");
|
||||||
|
import Endpoints = require("../../../../../shared/api");
|
||||||
|
|
||||||
|
export default function(req: Express.Request, res: Express.Response) {
|
||||||
|
res.render("already-logged-in", {
|
||||||
|
logout_endpoint: Endpoints.LOGOUT_GET
|
||||||
|
});
|
||||||
|
}
|
|
@ -4,15 +4,25 @@ import Endpoints = require("../../../../../shared/api");
|
||||||
import FirstFactorBlocker = require("../FirstFactorBlocker");
|
import FirstFactorBlocker = require("../FirstFactorBlocker");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||||
|
import AuthenticationSession = require("../../AuthenticationSession");
|
||||||
|
|
||||||
const TEMPLATE_NAME = "secondfactor";
|
const TEMPLATE_NAME = "secondfactor";
|
||||||
|
|
||||||
export default FirstFactorBlocker.default(handler);
|
export default FirstFactorBlocker.default(handler);
|
||||||
|
|
||||||
function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
|
function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
|
||||||
|
return AuthenticationSession.get(req)
|
||||||
|
.then(function (authSession) {
|
||||||
|
if (authSession.first_factor && authSession.second_factor) {
|
||||||
|
res.redirect(Endpoints.LOGGED_IN);
|
||||||
|
return BluebirdPromise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
res.render(TEMPLATE_NAME, {
|
res.render(TEMPLATE_NAME, {
|
||||||
|
username: authSession.userid,
|
||||||
totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
|
totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
|
||||||
u2f_identity_start_endpoint: Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET
|
u2f_identity_start_endpoint: Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET
|
||||||
});
|
});
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
|
});
|
||||||
}
|
}
|
|
@ -10,6 +10,7 @@ import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||||
import AuthenticationSession = require("../../AuthenticationSession");
|
import AuthenticationSession = require("../../AuthenticationSession");
|
||||||
import Constants = require("../../../../../shared/constants");
|
import Constants = require("../../../../../shared/constants");
|
||||||
import Util = require("util");
|
import Util = require("util");
|
||||||
|
import { DomainExtractor } from "../../utils/DomainExtractor";
|
||||||
|
|
||||||
const FIRST_FACTOR_NOT_VALIDATED_MESSAGE = "First factor not yet validated";
|
const FIRST_FACTOR_NOT_VALIDATED_MESSAGE = "First factor not yet validated";
|
||||||
const SECOND_FACTOR_NOT_VALIDATED_MESSAGE = "Second factor not yet validated";
|
const SECOND_FACTOR_NOT_VALIDATED_MESSAGE = "Second factor not yet validated";
|
||||||
|
@ -17,6 +18,7 @@ const SECOND_FACTOR_NOT_VALIDATED_MESSAGE = "Second factor not yet validated";
|
||||||
function verify_filter(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
function verify_filter(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||||
const accessController = ServerVariablesHandler.getAccessController(req.app);
|
const accessController = ServerVariablesHandler.getAccessController(req.app);
|
||||||
|
const authenticationMethodsCalculator = ServerVariablesHandler.getAuthenticationMethodCalculator(req.app);
|
||||||
|
|
||||||
return AuthenticationSession.get(req)
|
return AuthenticationSession.get(req)
|
||||||
.then(function (authSession) {
|
.then(function (authSession) {
|
||||||
|
@ -29,12 +31,11 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
|
||||||
return BluebirdPromise.reject(
|
return BluebirdPromise.reject(
|
||||||
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE));
|
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE));
|
||||||
|
|
||||||
const onlyBasicAuth = req.query[Constants.ONLY_BASIC_AUTH_QUERY_PARAM] === "true";
|
|
||||||
|
|
||||||
const host = objectPath.get<express.Request, string>(req, "headers.host");
|
const host = objectPath.get<express.Request, string>(req, "headers.host");
|
||||||
const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri");
|
const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri");
|
||||||
|
|
||||||
const domain = host.split(":")[0];
|
const domain = DomainExtractor.fromHostHeader(host);
|
||||||
|
const authenticationMethod = authenticationMethodsCalculator.compute(domain);
|
||||||
logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, path,
|
logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, path,
|
||||||
username, groups.join(","));
|
username, groups.join(","));
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
|
||||||
new exceptions.DomainAccessDenied(Util.format("User '%s' does not have access to '%'",
|
new exceptions.DomainAccessDenied(Util.format("User '%s' does not have access to '%'",
|
||||||
username, domain)));
|
username, domain)));
|
||||||
|
|
||||||
if (!onlyBasicAuth && !authSession.second_factor)
|
if (authenticationMethod == "two_factor" && !authSession.second_factor)
|
||||||
return BluebirdPromise.reject(
|
return BluebirdPromise.reject(
|
||||||
new exceptions.AccessDeniedError(SECOND_FACTOR_NOT_VALIDATED_MESSAGE));
|
new exceptions.AccessDeniedError(SECOND_FACTOR_NOT_VALIDATED_MESSAGE));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
export class DomainExtractor {
|
||||||
|
static fromUrl(url: string): string {
|
||||||
|
if (!url) return "";
|
||||||
|
return url.match(/https?:\/\/([^\/:]+).*/)[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromHostHeader(host: string): string {
|
||||||
|
if (!host) return "";
|
||||||
|
return host.split(":")[0];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
export class ObjectCloner {
|
||||||
|
static clone(obj: any): any {
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,5 +26,5 @@ html
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
script(src="/js/authelia.min.js")
|
script(src="/js/authelia.js")
|
||||||
block entrypoint
|
block entrypoint
|
|
@ -5,6 +5,7 @@ block form-header
|
||||||
<img class="header-img" src="../img/padlock.png" alt="">
|
<img class="header-img" src="../img/padlock.png" alt="">
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
p Hi <b>#{username}</b>, please complete second factor or <a href="/logout">logout</a>.
|
||||||
<div class="notification notification-totp"></div>
|
<div class="notification notification-totp"></div>
|
||||||
<form class="form-signin totp">
|
<form class="form-signin totp">
|
||||||
<div class="form-inputs">
|
<div class="form-inputs">
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { AuthenticationMethodCalculator } from "../src/lib/AuthenticationMethodCalculator";
|
||||||
|
import { AuthenticationMethodsConfiguration } from "../src/lib/configuration/Configuration";
|
||||||
|
import Assert = require("assert");
|
||||||
|
|
||||||
|
describe("test authentication method calculator", function() {
|
||||||
|
it("should return default method when sub domain not overriden", function() {
|
||||||
|
const options1: AuthenticationMethodsConfiguration = {
|
||||||
|
default_method: "two_factor",
|
||||||
|
per_subdomain_methods: {}
|
||||||
|
};
|
||||||
|
const options2: AuthenticationMethodsConfiguration = {
|
||||||
|
default_method: "basic_auth",
|
||||||
|
per_subdomain_methods: {}
|
||||||
|
};
|
||||||
|
const calculator1 = new AuthenticationMethodCalculator(options1);
|
||||||
|
const calculator2 = new AuthenticationMethodCalculator(options2);
|
||||||
|
Assert.equal(calculator1.compute("www.example.com"), "two_factor");
|
||||||
|
Assert.equal(calculator2.compute("www.example.com"), "basic_auth");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return overridden method when sub domain method is defined", function() {
|
||||||
|
const options1: AuthenticationMethodsConfiguration = {
|
||||||
|
default_method: "two_factor",
|
||||||
|
per_subdomain_methods: {
|
||||||
|
"www.example.com": "basic_auth"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const calculator1 = new AuthenticationMethodCalculator(options1);
|
||||||
|
Assert.equal(calculator1.compute("www.example.com"), "basic_auth");
|
||||||
|
});
|
||||||
|
});
|
|
@ -48,6 +48,10 @@ describe("test session configuration builder", function () {
|
||||||
local: {
|
local: {
|
||||||
in_memory: true
|
in_memory: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
authentication_methods: {
|
||||||
|
default_method: "two_factor",
|
||||||
|
per_subdomain_methods: {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -122,6 +126,10 @@ describe("test session configuration builder", function () {
|
||||||
local: {
|
local: {
|
||||||
in_memory: true
|
in_memory: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
authentication_methods: {
|
||||||
|
default_method: "two_factor",
|
||||||
|
per_subdomain_methods: {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { AuthenticationMethodsAdapter } from "../../../src/lib/configuration/adapters/AuthenticationMethodsAdapter";
|
||||||
|
import Assert = require("assert");
|
||||||
|
|
||||||
|
describe("test authentication methods configuration adapter", function () {
|
||||||
|
describe("no authentication methods defined", function () {
|
||||||
|
it("should adapt a configuration when no authentication methods config is defined", function () {
|
||||||
|
const userConfiguration: any = undefined;
|
||||||
|
|
||||||
|
const appConfiguration = AuthenticationMethodsAdapter.adapt(userConfiguration);
|
||||||
|
Assert.deepStrictEqual(appConfiguration, {
|
||||||
|
default_method: "two_factor",
|
||||||
|
per_subdomain_methods: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("partial authentication methods config", function() {
|
||||||
|
it("should adapt a configuration when default_method is not defined", function () {
|
||||||
|
const userConfiguration: any = {
|
||||||
|
per_subdomain_methods: {
|
||||||
|
"example.com": "basic_auth"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const appConfiguration = AuthenticationMethodsAdapter.adapt(userConfiguration);
|
||||||
|
Assert.deepStrictEqual(appConfiguration, {
|
||||||
|
default_method: "two_factor",
|
||||||
|
per_subdomain_methods: {
|
||||||
|
"example.com": "basic_auth"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should adapt a configuration when per_subdomain_methods is not defined", function () {
|
||||||
|
const userConfiguration: any = {
|
||||||
|
default_method: "basic_auth"
|
||||||
|
};
|
||||||
|
|
||||||
|
const appConfiguration = AuthenticationMethodsAdapter.adapt(userConfiguration);
|
||||||
|
Assert.deepStrictEqual(appConfiguration, {
|
||||||
|
default_method: "basic_auth",
|
||||||
|
per_subdomain_methods: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should adapt a configuration when per_subdomain_methods has wrong type", function () {
|
||||||
|
const userConfiguration: any = {
|
||||||
|
default_method: "basic_auth",
|
||||||
|
per_subdomain_methods: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const appConfiguration = AuthenticationMethodsAdapter.adapt(userConfiguration);
|
||||||
|
Assert.deepStrictEqual(appConfiguration, {
|
||||||
|
default_method: "basic_auth",
|
||||||
|
per_subdomain_methods: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,6 +2,7 @@ import Sinon = require("sinon");
|
||||||
import express = require("express");
|
import express = require("express");
|
||||||
import { RequestLoggerStub } from "./RequestLoggerStub";
|
import { RequestLoggerStub } from "./RequestLoggerStub";
|
||||||
import { UserDataStoreStub } from "./storage/UserDataStoreStub";
|
import { UserDataStoreStub } from "./storage/UserDataStoreStub";
|
||||||
|
import { AuthenticationMethodCalculator } from "../../src/lib/AuthenticationMethodCalculator";
|
||||||
import { VARIABLES_KEY } from "../../src/lib/ServerVariablesHandler";
|
import { VARIABLES_KEY } from "../../src/lib/ServerVariablesHandler";
|
||||||
|
|
||||||
export interface ServerVariablesMock {
|
export interface ServerVariablesMock {
|
||||||
|
@ -17,6 +18,7 @@ export interface ServerVariablesMock {
|
||||||
regulator: any;
|
regulator: any;
|
||||||
config: any;
|
config: any;
|
||||||
accessController: any;
|
accessController: any;
|
||||||
|
authenticationMethodsCalculator: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,7 +35,11 @@ export function mock(app: express.Application): ServerVariablesMock {
|
||||||
totpGenerator: Sinon.stub(),
|
totpGenerator: Sinon.stub(),
|
||||||
totpValidator: Sinon.stub(),
|
totpValidator: Sinon.stub(),
|
||||||
u2f: Sinon.stub(),
|
u2f: Sinon.stub(),
|
||||||
userDataStore: new UserDataStoreStub()
|
userDataStore: new UserDataStoreStub(),
|
||||||
|
authenticationMethodsCalculator: new AuthenticationMethodCalculator({
|
||||||
|
default_method: "two_factor",
|
||||||
|
per_subdomain_methods: {}
|
||||||
|
})
|
||||||
};
|
};
|
||||||
app.get = Sinon.stub().withArgs(VARIABLES_KEY).returns(mocks);
|
app.get = Sinon.stub().withArgs(VARIABLES_KEY).returns(mocks);
|
||||||
return mocks;
|
return mocks;
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
import VerifyGet = require("../../../src/lib/routes/verify/get");
|
import VerifyGet = require("../../../src/lib/routes/verify/get");
|
||||||
import AuthenticationSession = require("../../../src/lib/AuthenticationSession");
|
import AuthenticationSession = require("../../../src/lib/AuthenticationSession");
|
||||||
|
import { AuthenticationMethodCalculator } from "../../../src/lib/AuthenticationMethodCalculator";
|
||||||
|
import { AuthenticationMethodsConfiguration } from "../../../src/lib/configuration/Configuration";
|
||||||
|
|
||||||
import Sinon = require("sinon");
|
import Sinon = require("sinon");
|
||||||
import winston = require("winston");
|
import winston = require("winston");
|
||||||
|
@ -17,6 +19,7 @@ describe("test authentication token verification", function () {
|
||||||
let req: ExpressMock.RequestMock;
|
let req: ExpressMock.RequestMock;
|
||||||
let res: ExpressMock.ResponseMock;
|
let res: ExpressMock.ResponseMock;
|
||||||
let accessController: AccessControllerStub;
|
let accessController: AccessControllerStub;
|
||||||
|
let mocks: any;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
accessController = new AccessControllerStub();
|
accessController = new AccessControllerStub();
|
||||||
|
@ -34,9 +37,16 @@ describe("test authentication token verification", function () {
|
||||||
AuthenticationSession.reset(req as any);
|
AuthenticationSession.reset(req as any);
|
||||||
req.headers = {};
|
req.headers = {};
|
||||||
req.headers.host = "secret.example.com";
|
req.headers.host = "secret.example.com";
|
||||||
const mocks = ServerVariablesMock.mock(req.app);
|
mocks = ServerVariablesMock.mock(req.app);
|
||||||
mocks.config = {} as any;
|
mocks.config = {} as any;
|
||||||
mocks.accessController = accessController as any;
|
mocks.accessController = accessController as any;
|
||||||
|
const options: AuthenticationMethodsConfiguration = {
|
||||||
|
default_method: "two_factor",
|
||||||
|
per_subdomain_methods: {
|
||||||
|
"redirect.url": "basic_auth"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mocks.authenticationMethodsCalculator = new AuthenticationMethodCalculator(options);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be already authenticated", function () {
|
it("should be already authenticated", function () {
|
||||||
|
@ -153,9 +163,9 @@ describe("test authentication token verification", function () {
|
||||||
describe("given user tries to access a basic auth endpoint", function () {
|
describe("given user tries to access a basic auth endpoint", function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
req.query = {
|
req.query = {
|
||||||
redirect: "http://redirect.url",
|
redirect: "http://redirect.url"
|
||||||
only_basic_auth: "true"
|
|
||||||
};
|
};
|
||||||
|
req.headers["host"] = "redirect.url";
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be authenticated when first factor is validated and not second factor", function () {
|
it("should be authenticated when first factor is validated and not second factor", function () {
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { DomainExtractor } from "../../src/lib/utils/DomainExtractor";
|
||||||
|
import Assert = require("assert");
|
||||||
|
|
||||||
|
describe("test DomainExtractor", function () {
|
||||||
|
describe("test fromUrl", function () {
|
||||||
|
it("should return domain from https url", function () {
|
||||||
|
const domain = DomainExtractor.fromUrl("https://www.example.com/test/abc");
|
||||||
|
Assert.equal(domain, "www.example.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return domain from http url", function () {
|
||||||
|
const domain = DomainExtractor.fromUrl("http://www.example.com/test/abc");
|
||||||
|
Assert.equal(domain, "www.example.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return domain when url contains port", function () {
|
||||||
|
const domain = DomainExtractor.fromUrl("https://www.example.com:8080/test/abc");
|
||||||
|
Assert.equal(domain, "www.example.com");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("test fromHostHeader", function () {
|
||||||
|
it("should return domain when default port is used", function () {
|
||||||
|
const domain = DomainExtractor.fromHostHeader("www.example.com");
|
||||||
|
Assert.equal(domain, "www.example.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return domain when non default port is used", function () {
|
||||||
|
const domain = DomainExtractor.fromHostHeader("www.example.com:8080");
|
||||||
|
Assert.equal(domain, "www.example.com");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -297,3 +297,5 @@ export const LOGOUT_GET = "/logout";
|
||||||
export const ERROR_401_GET = "/error/401";
|
export const ERROR_401_GET = "/error/401";
|
||||||
export const ERROR_403_GET = "/error/403";
|
export const ERROR_403_GET = "/error/403";
|
||||||
export const ERROR_404_GET = "/error/404";
|
export const ERROR_404_GET = "/error/404";
|
||||||
|
|
||||||
|
export const LOGGED_IN = "/loggedin";
|
||||||
|
|
|
@ -1,4 +1 @@
|
||||||
|
|
||||||
|
|
||||||
export const ONLY_BASIC_AUTH_QUERY_PARAM = "only_basic_auth";
|
|
||||||
export const REDIRECT_QUERY_PARAM = "redirect";
|
export const REDIRECT_QUERY_PARAM = "redirect";
|
|
@ -0,0 +1,32 @@
|
||||||
|
Feature: User is redirected when factors are already validated
|
||||||
|
|
||||||
|
@need-registered-user-john
|
||||||
|
Scenario: User has validated first factor and tries to access service protected by second factor. He is then redirect to second factor step.
|
||||||
|
When I visit "https://basicauth.test.local:8080/secret.html"
|
||||||
|
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fbasicauth.test.local%3A8080%2Fsecret.html"
|
||||||
|
And I login with user "john" and password "password"
|
||||||
|
And I'm redirected to "https://basicauth.test.local:8080/secret.html"
|
||||||
|
And I visit "https://public.test.local:8080/secret.html"
|
||||||
|
Then I'm redirected to "https://auth.test.local:8080/secondfactor?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
|
||||||
|
|
||||||
|
@need-registered-user-john
|
||||||
|
Scenario: User who has validated second factor and access auth portal should be redirected to "Already logged in page"
|
||||||
|
When I visit "https://public.test.local:8080/secret.html"
|
||||||
|
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
|
||||||
|
And I login with user "john" and password "password"
|
||||||
|
And I use "REGISTERED" as TOTP token handle
|
||||||
|
And I click on "TOTP"
|
||||||
|
And I'm redirected to "https://public.test.local:8080/secret.html"
|
||||||
|
And I visit "https://auth.test.local:8080"
|
||||||
|
Then I'm redirected to "https://auth.test.local:8080/loggedin"
|
||||||
|
|
||||||
|
@need-registered-user-john
|
||||||
|
Scenario: User who has validated second factor and access auth portal with rediction param should be redirected to that URL
|
||||||
|
When I visit "https://public.test.local:8080/secret.html"
|
||||||
|
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
|
||||||
|
And I login with user "john" and password "password"
|
||||||
|
And I use "REGISTERED" as TOTP token handle
|
||||||
|
And I click on "TOTP"
|
||||||
|
And I'm redirected to "https://public.test.local:8080/secret.html"
|
||||||
|
And I visit "https://auth.test.local:8080?redirect=https://public.test.local:8080/secret.html"
|
||||||
|
Then I'm redirected to "https://public.test.local:8080/secret.html"
|
|
@ -1,4 +1,4 @@
|
||||||
Feature: User validate first factor
|
Feature: Authentication scenarii
|
||||||
|
|
||||||
Scenario: User succeeds first factor
|
Scenario: User succeeds first factor
|
||||||
Given I visit "https://auth.test.local:8080/"
|
Given I visit "https://auth.test.local:8080/"
|
||||||
|
|
|
@ -2,18 +2,12 @@ Feature: User can access certain subdomains with basic auth
|
||||||
|
|
||||||
@need-registered-user-john
|
@need-registered-user-john
|
||||||
Scenario: User is redirected to service after first factor if allowed
|
Scenario: User is redirected to service after first factor if allowed
|
||||||
When I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fbasicauth.test.local%3A8080%2Fsecret.html&only_basic_auth=true"
|
When I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fbasicauth.test.local%3A8080%2Fsecret.html"
|
||||||
And I login with user "john" and password "password"
|
And I login with user "john" and password "password"
|
||||||
Then I'm redirected to "https://basicauth.test.local:8080/secret.html"
|
Then I'm redirected to "https://basicauth.test.local:8080/secret.html"
|
||||||
|
|
||||||
@need-registered-user-john
|
@need-registered-user-john
|
||||||
Scenario: Redirection after first factor fails if basic_auth not allowed. It redirects user to first factor.
|
Scenario: Redirection after first factor fails if basic_auth not allowed. It redirects user to first factor.
|
||||||
When I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html&only_basic_auth=true"
|
|
||||||
And I login with user "john" and password "password"
|
|
||||||
Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
|
||||||
|
|
||||||
@need-registered-user-john
|
|
||||||
Scenario: User is redirected to second factor after first factor
|
|
||||||
When I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
When I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
||||||
And I login with user "john" and password "password"
|
And I login with user "john" and password "password"
|
||||||
Then I'm redirected to "https://auth.test.local:8080/secondfactor?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
|
||||||
|
|
Loading…
Reference in New Issue