From 658640211446547a64b1e8d83030e78c4b044393 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Mon, 4 Dec 2017 22:39:55 +0100 Subject: [PATCH] Support 'redirect' in /api/verify endpoint to support Traefik Traefik handles auth forwarding but does not manage redirections like Nginx. Therefore, Authelia must redirect the user and Traefik will forward this request. To support both Nginx and Traefik, /api/verify is now configurable with the 'redirect' get parameter. If the verification fails and 'redirect' is not provided the response will be a 401 error as before. If the parameter is provided and set to any URL, the response will be a redirection (302) to this URL. --- .travis.yml | 1 + README.md | 6 +- doc/api_data.js | 69 ++++++++++++++-------- doc/api_data.json | 69 ++++++++++++++-------- doc/api_project.js | 4 +- doc/api_project.json | 4 +- example/nginx/portal/nginx.conf | 20 +++++++ server/src/lib/ErrorReplies.ts | 9 +++ server/src/lib/routes/verify/get.ts | 17 +++++- server/test/routes/verify/get.test.ts | 37 ++++++++++-- shared/api.ts | 13 ++-- test/features/authelia.feature | 9 +++ test/features/step_definitions/authelia.ts | 51 ++++++++++++++++ 13 files changed, 244 insertions(+), 65 deletions(-) create mode 100644 test/features/authelia.feature create mode 100644 test/features/step_definitions/authelia.ts diff --git a/.travis.yml b/.travis.yml index 0b8fecdb1..27105ca44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ addons: - mx1.mail.example.com - mx2.mail.example.com - public.example.com + - authelia.example.com before_install: - npm install -g npm@'>=2.13.5' diff --git a/README.md b/README.md index 682b5ce3b..b70d69a71 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ [![Build](https://travis-ci.org/clems4ever/authelia.svg?branch=master)](https://travis-ci.org/clems4ever/authelia) [![Gitter](https://img.shields.io/gitter/room/badges/shields.svg)](https://gitter.im/authelia/general?utm_source=share-link&utm_medium=link&utm_campaign=share-link) -**Authelia** is a complete HTTP 2-factor authentication server for proxies like -nginx. It has been made to work with nginx [auth_request] module and is currently -used in production to secure internal services in a small docker swarm cluster. +**Authelia** is a complete HTTP 2-factor authentication server for proxies like +Nginx or Traefik. It has been designed to be proxy agnostic so that you can +use whichever proxy supporting authentication forwarding. # Table of Contents 1. [Features summary](#features-summary) diff --git a/doc/api_data.js b/doc/api_data.js index 2eacc2aaf..7aba8ada3 100644 --- a/doc/api_data.js +++ b/doc/api_data.js @@ -20,7 +20,7 @@ define({ "api": [ } }, "description": "

Serves the login page and create a create a cookie for the client.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "Authentication" }, { @@ -56,7 +56,7 @@ define({ "api": [ } }, "description": "

Log out the user and redirect to the URL.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "Authentication" }, { @@ -80,7 +80,7 @@ define({ "api": [ } }, "description": "

Serves the second factor page

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "Authentication" }, { @@ -145,7 +145,7 @@ define({ "api": [ } }, "description": "

Verify credentials against the LDAP.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "Authentication", "header": { "fields": { @@ -169,7 +169,7 @@ define({ "api": [ "group": "PasswordReset", "version": "1.0.0", "description": "

Start password reset request.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "PasswordReset", "header": { "fields": { @@ -240,7 +240,7 @@ define({ "api": [ "group": "PasswordReset", "version": "1.0.0", "description": "

Serve a page that requires the username.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "PasswordReset", "header": { "fields": { @@ -277,7 +277,7 @@ define({ "api": [ } }, "description": "

Set a new password for the user.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "PasswordReset", "header": { "fields": { @@ -301,7 +301,7 @@ define({ "api": [ "group": "PasswordReset", "version": "1.0.0", "description": "

Start password reset request.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "PasswordReset", "header": { "fields": { @@ -366,7 +366,7 @@ define({ "api": [ "group": "TOTP", "version": "1.0.0", "description": "

Serves the TOTP registration page that displays the secret. The secret is a QRCode and a base32 secret.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "TOTP", "header": { "fields": { @@ -437,7 +437,7 @@ define({ "api": [ "group": "TOTP", "version": "1.0.0", "description": "

Initiates the identity validation

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "TOTP", "header": { "fields": { @@ -521,7 +521,7 @@ define({ "api": [ "group": "Success 302", "optional": false, "field": "Redirect", - "description": "

to the URL that has been stored during last call to /verify.

" + "description": "

to the URL that has been stored during last call to /api/verify.

" } ] } @@ -549,7 +549,7 @@ define({ "api": [ } }, "description": "

Verify TOTP token. The user is authenticated upon success.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "TOTP", "header": { "fields": { @@ -579,7 +579,7 @@ define({ "api": [ "group": "Success 302", "optional": false, "field": "Redirect", - "description": "

to the URL that has been stored during last call to /verify.

" + "description": "

to the URL that has been stored during last call to /api/verify.

" } ] } @@ -607,7 +607,7 @@ define({ "api": [ } }, "description": "

Complete authentication request of the U2F device.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -637,13 +637,13 @@ define({ "api": [ "group": "Success 302", "optional": false, "field": "Redirect", - "description": "

to the URL that has been stored during last call to /verify.

" + "description": "

to the URL that has been stored during last call to /api/verify.

" } ] } }, "description": "

Complete U2F registration request.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -679,7 +679,7 @@ define({ "api": [ "name": "RequestU2FRegistration", "group": "U2F", "version": "1.0.0", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -745,7 +745,7 @@ define({ "api": [ "group": "U2F", "version": "1.0.0", "description": "

Serves the U2F registration page that asks the user to touch the token of the U2F device.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -850,7 +850,7 @@ define({ "api": [ } }, "description": "

Initiate an authentication request using a U2F device.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -908,7 +908,7 @@ define({ "api": [ } }, "description": "

Initiate a U2F device registration request.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -926,11 +926,24 @@ define({ "api": [ }, { "type": "get", - "url": "/verify", + "url": "/api/verify", "title": "Verify user authentication", "name": "VerifyAuthentication", "group": "Verification", "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "redirect", + "description": "

Optional parameter set to the url where the user is redirected if access is refused. It is mainly used by Traefik that does not control the redirection itself.

" + } + ] + } + }, "success": { "fields": { "Success 204": [ @@ -945,18 +958,26 @@ define({ "api": [ }, "error": { "fields": { + "Error 302": [ + { + "group": "Error 302", + "optional": false, + "field": "redirect", + "description": "

The user is redirected if redirect parameter is provided.

" + } + ], "Error 401": [ { "group": "Error 401", "optional": false, "field": "status", - "description": "

The user is not authenticated.

" + "description": "

The user get an error if access failed

" } ] } }, - "description": "

Verify that the user is authenticated, i.e., the two factors have been validated

", - "filename": "src/server/endpoints.ts", + "description": "

Verify that the user is authenticated, i.e., the two factors have been validated. If the user is authenticated the response headers Remote-User and Remote-Groups are set. Remote-User contains the user id of the currently logged in user and Remote-Groups a comma separated list of assigned groups.

", + "filename": "shared/api.ts", "groupTitle": "Verification", "header": { "fields": { diff --git a/doc/api_data.json b/doc/api_data.json index 527b67508..c6935ffe7 100644 --- a/doc/api_data.json +++ b/doc/api_data.json @@ -20,7 +20,7 @@ } }, "description": "

Serves the login page and create a create a cookie for the client.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "Authentication" }, { @@ -56,7 +56,7 @@ } }, "description": "

Log out the user and redirect to the URL.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "Authentication" }, { @@ -80,7 +80,7 @@ } }, "description": "

Serves the second factor page

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "Authentication" }, { @@ -145,7 +145,7 @@ } }, "description": "

Verify credentials against the LDAP.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "Authentication", "header": { "fields": { @@ -169,7 +169,7 @@ "group": "PasswordReset", "version": "1.0.0", "description": "

Start password reset request.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "PasswordReset", "header": { "fields": { @@ -240,7 +240,7 @@ "group": "PasswordReset", "version": "1.0.0", "description": "

Serve a page that requires the username.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "PasswordReset", "header": { "fields": { @@ -277,7 +277,7 @@ } }, "description": "

Set a new password for the user.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "PasswordReset", "header": { "fields": { @@ -301,7 +301,7 @@ "group": "PasswordReset", "version": "1.0.0", "description": "

Start password reset request.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "PasswordReset", "header": { "fields": { @@ -366,7 +366,7 @@ "group": "TOTP", "version": "1.0.0", "description": "

Serves the TOTP registration page that displays the secret. The secret is a QRCode and a base32 secret.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "TOTP", "header": { "fields": { @@ -437,7 +437,7 @@ "group": "TOTP", "version": "1.0.0", "description": "

Initiates the identity validation

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "TOTP", "header": { "fields": { @@ -521,7 +521,7 @@ "group": "Success 302", "optional": false, "field": "Redirect", - "description": "

to the URL that has been stored during last call to /verify.

" + "description": "

to the URL that has been stored during last call to /api/verify.

" } ] } @@ -549,7 +549,7 @@ } }, "description": "

Verify TOTP token. The user is authenticated upon success.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "TOTP", "header": { "fields": { @@ -579,7 +579,7 @@ "group": "Success 302", "optional": false, "field": "Redirect", - "description": "

to the URL that has been stored during last call to /verify.

" + "description": "

to the URL that has been stored during last call to /api/verify.

" } ] } @@ -607,7 +607,7 @@ } }, "description": "

Complete authentication request of the U2F device.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -637,13 +637,13 @@ "group": "Success 302", "optional": false, "field": "Redirect", - "description": "

to the URL that has been stored during last call to /verify.

" + "description": "

to the URL that has been stored during last call to /api/verify.

" } ] } }, "description": "

Complete U2F registration request.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -679,7 +679,7 @@ "name": "RequestU2FRegistration", "group": "U2F", "version": "1.0.0", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -745,7 +745,7 @@ "group": "U2F", "version": "1.0.0", "description": "

Serves the U2F registration page that asks the user to touch the token of the U2F device.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -850,7 +850,7 @@ } }, "description": "

Initiate an authentication request using a U2F device.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -908,7 +908,7 @@ } }, "description": "

Initiate a U2F device registration request.

", - "filename": "src/server/endpoints.ts", + "filename": "shared/api.ts", "groupTitle": "U2F", "header": { "fields": { @@ -926,11 +926,24 @@ }, { "type": "get", - "url": "/verify", + "url": "/api/verify", "title": "Verify user authentication", "name": "VerifyAuthentication", "group": "Verification", "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "redirect", + "description": "

Optional parameter set to the url where the user is redirected if access is refused. It is mainly used by Traefik that does not control the redirection itself.

" + } + ] + } + }, "success": { "fields": { "Success 204": [ @@ -945,18 +958,26 @@ }, "error": { "fields": { + "Error 302": [ + { + "group": "Error 302", + "optional": false, + "field": "redirect", + "description": "

The user is redirected if redirect parameter is provided.

" + } + ], "Error 401": [ { "group": "Error 401", "optional": false, "field": "status", - "description": "

The user is not authenticated.

" + "description": "

The user get an error if access failed

" } ] } }, - "description": "

Verify that the user is authenticated, i.e., the two factors have been validated

", - "filename": "src/server/endpoints.ts", + "description": "

Verify that the user is authenticated, i.e., the two factors have been validated. If the user is authenticated the response headers Remote-User and Remote-Groups are set. Remote-User contains the user id of the currently logged in user and Remote-Groups a comma separated list of assigned groups.

", + "filename": "shared/api.ts", "groupTitle": "Verification", "header": { "fields": { diff --git a/doc/api_project.js b/doc/api_project.js index 9b4ecf09d..727258a09 100644 --- a/doc/api_project.js +++ b/doc/api_project.js @@ -1,14 +1,14 @@ define({ "title": "Authelia API documentation", "name": "authelia", - "version": "2.1.3", + "version": "3.7.0", "description": "2FA Single Sign-On server for nginx using LDAP, TOTP and U2F", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2017-06-11T20:41:36.025Z", + "time": "2017-12-04T21:38:44.927Z", "url": "http://apidocjs.com", "version": "0.17.6" } diff --git a/doc/api_project.json b/doc/api_project.json index b27e7e63e..a3017b24f 100644 --- a/doc/api_project.json +++ b/doc/api_project.json @@ -1,14 +1,14 @@ { "title": "Authelia API documentation", "name": "authelia", - "version": "2.1.3", + "version": "3.7.0", "description": "2FA Single Sign-On server for nginx using LDAP, TOTP and U2F", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2017-06-11T20:41:36.025Z", + "time": "2017-12-04T21:38:44.927Z", "url": "http://apidocjs.com", "version": "0.17.6" } diff --git a/example/nginx/portal/nginx.conf b/example/nginx/portal/nginx.conf index c7d4016b0..1ce5fcfc9 100644 --- a/example/nginx/portal/nginx.conf +++ b/example/nginx/portal/nginx.conf @@ -322,5 +322,25 @@ http { proxy_pass $upstream_headers; } } + + server { + listen 443 ssl; + server_name authelia.example.com; + + resolver 127.0.0.11 ipv6=off; + set $upstream_endpoint http://authelia; + + ssl on; + ssl_certificate /etc/ssl/server.crt; + ssl_certificate_key /etc/ssl/server.key; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "SAMEORIGIN"; + + location / { + proxy_set_header Host $http_host; + proxy_pass $upstream_endpoint; + } + } } diff --git a/server/src/lib/ErrorReplies.ts b/server/src/lib/ErrorReplies.ts index dc74abbe0..f1c5f4fd1 100644 --- a/server/src/lib/ErrorReplies.ts +++ b/server/src/lib/ErrorReplies.ts @@ -19,6 +19,15 @@ function replyWithError(req: express.Request, res: express.Response, }; } +export function redirectTo(redirectUrl: string, req: express.Request, + res: express.Response, logger: IRequestLogger) { + return function(err: Error) { + logger.error(req, "Error: %s", err.message); + logger.debug(req, "Redirecting to %s", redirectUrl); + res.redirect(redirectUrl); + }; +} + export function replyWithError400(req: express.Request, res: express.Response, logger: IRequestLogger) { return replyWithError(req, res, 400, logger); diff --git a/server/src/lib/routes/verify/get.ts b/server/src/lib/routes/verify/get.ts index 54f0c2ec5..79fda877e 100644 --- a/server/src/lib/routes/verify/get.ts +++ b/server/src/lib/routes/verify/get.ts @@ -5,6 +5,7 @@ import ErrorReplies = require("../../ErrorReplies"); import { ServerVariables } from "../../ServerVariables"; import GetWithSessionCookieMethod from "./get_session_cookie"; import GetWithBasicAuthMethod from "./get_basic_auth"; +import Constants = require("../../../../../shared/constants"); import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler"; @@ -50,6 +51,12 @@ function replyWith200(res: Express.Response) { }; } +function getRedirectParam(req: Express.Request) { + return req.query[Constants.REDIRECT_QUERY_PARAM] != "undefined" + ? req.query[Constants.REDIRECT_QUERY_PARAM] + : undefined; +} + export default function (vars: ServerVariables) { return function (req: Express.Request, res: Express.Response) : BluebirdPromise { @@ -66,7 +73,15 @@ export default function (vars: ServerVariables) { .catch(Exceptions.DomainAccessDenied, ErrorReplies .replyWithError403(req, res, vars.logger)) // The user is not yet authenticated -> 401 - .catch(ErrorReplies.replyWithError401(req, res, vars.logger)); + .catch(function (err) { + const redirectUrl = getRedirectParam(req); + if (redirectUrl) { + ErrorReplies.redirectTo(redirectUrl, req, res, vars.logger)(err); + } + else { + ErrorReplies.replyWithError401(req, res, vars.logger)(err); + } + }); }; } diff --git a/server/test/routes/verify/get.test.ts b/server/test/routes/verify/get.test.ts index 099e763b6..d46a0af6a 100644 --- a/server/test/routes/verify/get.test.ts +++ b/server/test/routes/verify/get.test.ts @@ -24,11 +24,11 @@ describe("test /api/verify endpoint", function () { res = ExpressMock.ResponseMock(); req.originalUrl = "/api/xxxx"; req.query = { - redirect: "http://redirect.url" + redirect: "undefined" }; AuthenticationSessionHandler.reset(req as any); req.headers.host = "secret.example.com"; - const s = ServerVariablesMockBuilder.build(true); + const s = ServerVariablesMockBuilder.build(false); mocks = s.mocks; vars = s.variables; authSession = AuthenticationSessionHandler.get(req as any, vars.logger); @@ -147,9 +147,6 @@ describe("test /api/verify endpoint", function () { describe("given user tries to access a single factor endpoint", function () { beforeEach(function () { - req.query = { - redirect: "http://redirect.url" - }; req.headers["host"] = "redirect.url"; mocks.config.authentication_methods.per_subdomain_methods = { "redirect.url": "single_factor" @@ -220,6 +217,36 @@ describe("test /api/verify endpoint", function () { }); }); + describe("response type 401 | 302", function() { + it("should return error code 401", function() { + mocks.accessController.isAccessAllowedMock.returns(true); + mocks.config.authentication_methods.default_method = "single_factor"; + mocks.ldapAuthenticator.authenticateStub.rejects(new Error( + "Invalid credentials")); + req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA=="; + + return VerifyGet.default(vars)(req as express.Request, res as any) + .then(function () { + Assert(res.status.calledWithExactly(401)); + }); + }); + + it("should redirect to provided redirection url", function() { + const REDIRECT_URL = "http://redirection_url.com"; + mocks.accessController.isAccessAllowedMock.returns(true); + mocks.config.authentication_methods.default_method = "single_factor"; + mocks.ldapAuthenticator.authenticateStub.rejects(new Error( + "Invalid credentials")); + req.headers["proxy-authorization"] = "Basic am9objpwYXNzd29yZA=="; + req.query["redirect"] = REDIRECT_URL; + + return VerifyGet.default(vars)(req as express.Request, res as any) + .then(function () { + Assert(res.redirect.calledWithExactly(REDIRECT_URL)); + }); + }); + }); + describe("with basic auth", function () { it("should authenticate correctly", function () { mocks.accessController.isAccessAllowedMock.returns(true); diff --git a/shared/api.ts b/shared/api.ts index ef257f2c8..2ebe5d3d9 100644 --- a/shared/api.ts +++ b/shared/api.ts @@ -39,7 +39,7 @@ * @apiUse UserSession * @apiUse InternalError * - * @apiSuccess (Success 302) Redirect to the URL that has been stored during last call to /verify. + * @apiSuccess (Success 302) Redirect to the URL that has been stored during last call to /api/verify. * * @apiDescription Complete U2F registration request. */ @@ -68,7 +68,7 @@ export const SECOND_FACTOR_U2F_REGISTER_REQUEST_GET = "/api/u2f/register_request * @apiUse UserSession * @apiUse InternalError * - * @apiSuccess (Success 302) Redirect to the URL that has been stored during last call to /verify. + * @apiSuccess (Success 302) Redirect to the URL that has been stored during last call to /api/verify. * @apiError (Error 403) {none} error No authentication request has been provided. * * @apiDescription Complete authentication request of the U2F device. @@ -100,7 +100,7 @@ export const SECOND_FACTOR_U2F_SIGN_REQUEST_GET = "/api/u2f/sign_request"; * * @apiParam {String} token TOTP token. * - * @apiSuccess (Success 302) Redirect to the URL that has been stored during last call to /verify. + * @apiSuccess (Success 302) Redirect to the URL that has been stored during last call to /api/verify. * @apiError (Error 401) {none} error TOTP token is invalid. * * @apiDescription Verify TOTP token. The user is authenticated upon success. @@ -270,8 +270,13 @@ export const SECOND_FACTOR_GET = "/secondfactor"; * @apiVersion 1.0.0 * @apiUse UserSession * + * @apiParam {String} redirect Optional parameter set to the url where the user + * is redirected if access is refused. It is mainly used by Traefik that does + * not control the redirection itself. + * * @apiSuccess (Success 204) status The user is authenticated. - * @apiError (Error 401) status The user is not authenticated. + * @apiError (Error 302) redirect The user is redirected if redirect parameter is provided. + * @apiError (Error 401) status The user get an error if access failed * * @apiDescription Verify that the user is authenticated, i.e., the two * factors have been validated. diff --git a/test/features/authelia.feature b/test/features/authelia.feature new file mode 100644 index 000000000..7c7f472db --- /dev/null +++ b/test/features/authelia.feature @@ -0,0 +1,9 @@ +Feature: Generic tests on Authelia endpoints + + Scenario: /api/verify replies with error when redirect parameter is not provided + When I query "https://authelia.example.com:8080/api/verify" + Then I get error code 401 + + Scenario: /api/verify redirects when redirect parameter is provided + When I query "https://authelia.example.com:8080/api/verify?redirect=http://login.example.com:8080" + Then I get redirected to "http://login.example.com:8080" \ No newline at end of file diff --git a/test/features/step_definitions/authelia.ts b/test/features/step_definitions/authelia.ts new file mode 100644 index 000000000..da3f9676b --- /dev/null +++ b/test/features/step_definitions/authelia.ts @@ -0,0 +1,51 @@ +import Cucumber = require("cucumber"); +import seleniumWebdriver = require("selenium-webdriver"); +import Assert = require("assert"); +import Request = require("request-promise"); +import Bluebird = require("bluebird"); + +Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) { + Before(function () { + this.jar = Request.jar(); + }) + + When("I query {stringInDoubleQuotes}", function (url: string) { + const that = this; + return Request(url, { followRedirect: false }) + .then(function(response) { + console.log(response); + that.response = response; + }) + .catch(function(err: Error) { + that.error = err; + }) + }); + + Then("I get error code 401", function() { + const that = this; + return new Bluebird(function(resolve, reject) { + if(that.error && that.error.statusCode == 401) { + resolve(); + } + else { + if(that.response) + reject(new Error("No error thrown")); + else if(that.error.statusCode != 401) + reject(new Error("Error code != 401")); + } + }); + }); + + Then("I get redirected to {stringInDoubleQuotes}", function(url: string) { + const that = this; + return new Bluebird(function(resolve, reject) { + if(that.error && that.error.statusCode == 302 + && that.error.message.indexOf(url) > -1) { + resolve(); + } + else { + reject(new Error("Not redirected")); + } + }); + }) +}); \ No newline at end of file