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.
pull/204/head
Clement Michaud 2017-12-04 22:39:55 +01:00
parent 3f6c375446
commit 6586402114
13 changed files with 244 additions and 65 deletions

View File

@ -23,6 +23,7 @@ addons:
- mx1.mail.example.com - mx1.mail.example.com
- mx2.mail.example.com - mx2.mail.example.com
- public.example.com - public.example.com
- authelia.example.com
before_install: before_install:
- npm install -g npm@'>=2.13.5' - npm install -g npm@'>=2.13.5'

View File

@ -5,8 +5,8 @@
[![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) [![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 **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 Nginx or Traefik. It has been designed to be proxy agnostic so that you can
used in production to secure internal services in a small docker swarm cluster. use whichever proxy supporting authentication forwarding.
# Table of Contents # Table of Contents
1. [Features summary](#features-summary) 1. [Features summary](#features-summary)

View File

@ -20,7 +20,7 @@ define({ "api": [
} }
}, },
"description": "<p>Serves the login page and create a create a cookie for the client.</p>", "description": "<p>Serves the login page and create a create a cookie for the client.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "Authentication" "groupTitle": "Authentication"
}, },
{ {
@ -56,7 +56,7 @@ define({ "api": [
} }
}, },
"description": "<p>Log out the user and redirect to the URL.</p>", "description": "<p>Log out the user and redirect to the URL.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "Authentication" "groupTitle": "Authentication"
}, },
{ {
@ -80,7 +80,7 @@ define({ "api": [
} }
}, },
"description": "<p>Serves the second factor page</p>", "description": "<p>Serves the second factor page</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "Authentication" "groupTitle": "Authentication"
}, },
{ {
@ -145,7 +145,7 @@ define({ "api": [
} }
}, },
"description": "<p>Verify credentials against the LDAP.</p>", "description": "<p>Verify credentials against the LDAP.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "Authentication", "groupTitle": "Authentication",
"header": { "header": {
"fields": { "fields": {
@ -169,7 +169,7 @@ define({ "api": [
"group": "PasswordReset", "group": "PasswordReset",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Start password reset request.</p>", "description": "<p>Start password reset request.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "PasswordReset", "groupTitle": "PasswordReset",
"header": { "header": {
"fields": { "fields": {
@ -240,7 +240,7 @@ define({ "api": [
"group": "PasswordReset", "group": "PasswordReset",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Serve a page that requires the username.</p>", "description": "<p>Serve a page that requires the username.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "PasswordReset", "groupTitle": "PasswordReset",
"header": { "header": {
"fields": { "fields": {
@ -277,7 +277,7 @@ define({ "api": [
} }
}, },
"description": "<p>Set a new password for the user.</p>", "description": "<p>Set a new password for the user.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "PasswordReset", "groupTitle": "PasswordReset",
"header": { "header": {
"fields": { "fields": {
@ -301,7 +301,7 @@ define({ "api": [
"group": "PasswordReset", "group": "PasswordReset",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Start password reset request.</p>", "description": "<p>Start password reset request.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "PasswordReset", "groupTitle": "PasswordReset",
"header": { "header": {
"fields": { "fields": {
@ -366,7 +366,7 @@ define({ "api": [
"group": "TOTP", "group": "TOTP",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Serves the TOTP registration page that displays the secret. The secret is a QRCode and a base32 secret.</p>", "description": "<p>Serves the TOTP registration page that displays the secret. The secret is a QRCode and a base32 secret.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "TOTP", "groupTitle": "TOTP",
"header": { "header": {
"fields": { "fields": {
@ -437,7 +437,7 @@ define({ "api": [
"group": "TOTP", "group": "TOTP",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Initiates the identity validation</p>", "description": "<p>Initiates the identity validation</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "TOTP", "groupTitle": "TOTP",
"header": { "header": {
"fields": { "fields": {
@ -521,7 +521,7 @@ define({ "api": [
"group": "Success 302", "group": "Success 302",
"optional": false, "optional": false,
"field": "Redirect", "field": "Redirect",
"description": "<p>to the URL that has been stored during last call to /verify.</p>" "description": "<p>to the URL that has been stored during last call to /api/verify.</p>"
} }
] ]
} }
@ -549,7 +549,7 @@ define({ "api": [
} }
}, },
"description": "<p>Verify TOTP token. The user is authenticated upon success.</p>", "description": "<p>Verify TOTP token. The user is authenticated upon success.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "TOTP", "groupTitle": "TOTP",
"header": { "header": {
"fields": { "fields": {
@ -579,7 +579,7 @@ define({ "api": [
"group": "Success 302", "group": "Success 302",
"optional": false, "optional": false,
"field": "Redirect", "field": "Redirect",
"description": "<p>to the URL that has been stored during last call to /verify.</p>" "description": "<p>to the URL that has been stored during last call to /api/verify.</p>"
} }
] ]
} }
@ -607,7 +607,7 @@ define({ "api": [
} }
}, },
"description": "<p>Complete authentication request of the U2F device.</p>", "description": "<p>Complete authentication request of the U2F device.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -637,13 +637,13 @@ define({ "api": [
"group": "Success 302", "group": "Success 302",
"optional": false, "optional": false,
"field": "Redirect", "field": "Redirect",
"description": "<p>to the URL that has been stored during last call to /verify.</p>" "description": "<p>to the URL that has been stored during last call to /api/verify.</p>"
} }
] ]
} }
}, },
"description": "<p>Complete U2F registration request.</p>", "description": "<p>Complete U2F registration request.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -679,7 +679,7 @@ define({ "api": [
"name": "RequestU2FRegistration", "name": "RequestU2FRegistration",
"group": "U2F", "group": "U2F",
"version": "1.0.0", "version": "1.0.0",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -745,7 +745,7 @@ define({ "api": [
"group": "U2F", "group": "U2F",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Serves the U2F registration page that asks the user to touch the token of the U2F device.</p>", "description": "<p>Serves the U2F registration page that asks the user to touch the token of the U2F device.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -850,7 +850,7 @@ define({ "api": [
} }
}, },
"description": "<p>Initiate an authentication request using a U2F device.</p>", "description": "<p>Initiate an authentication request using a U2F device.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -908,7 +908,7 @@ define({ "api": [
} }
}, },
"description": "<p>Initiate a U2F device registration request.</p>", "description": "<p>Initiate a U2F device registration request.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -926,11 +926,24 @@ define({ "api": [
}, },
{ {
"type": "get", "type": "get",
"url": "/verify", "url": "/api/verify",
"title": "Verify user authentication", "title": "Verify user authentication",
"name": "VerifyAuthentication", "name": "VerifyAuthentication",
"group": "Verification", "group": "Verification",
"version": "1.0.0", "version": "1.0.0",
"parameter": {
"fields": {
"Parameter": [
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "redirect",
"description": "<p>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.</p>"
}
]
}
},
"success": { "success": {
"fields": { "fields": {
"Success 204": [ "Success 204": [
@ -945,18 +958,26 @@ define({ "api": [
}, },
"error": { "error": {
"fields": { "fields": {
"Error 302": [
{
"group": "Error 302",
"optional": false,
"field": "redirect",
"description": "<p>The user is redirected if redirect parameter is provided.</p>"
}
],
"Error 401": [ "Error 401": [
{ {
"group": "Error 401", "group": "Error 401",
"optional": false, "optional": false,
"field": "status", "field": "status",
"description": "<p>The user is not authenticated.</p>" "description": "<p>The user get an error if access failed</p>"
} }
] ]
} }
}, },
"description": "<p>Verify that the user is authenticated, i.e., the two factors have been validated</p>", "description": "<p>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.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "Verification", "groupTitle": "Verification",
"header": { "header": {
"fields": { "fields": {

View File

@ -20,7 +20,7 @@
} }
}, },
"description": "<p>Serves the login page and create a create a cookie for the client.</p>", "description": "<p>Serves the login page and create a create a cookie for the client.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "Authentication" "groupTitle": "Authentication"
}, },
{ {
@ -56,7 +56,7 @@
} }
}, },
"description": "<p>Log out the user and redirect to the URL.</p>", "description": "<p>Log out the user and redirect to the URL.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "Authentication" "groupTitle": "Authentication"
}, },
{ {
@ -80,7 +80,7 @@
} }
}, },
"description": "<p>Serves the second factor page</p>", "description": "<p>Serves the second factor page</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "Authentication" "groupTitle": "Authentication"
}, },
{ {
@ -145,7 +145,7 @@
} }
}, },
"description": "<p>Verify credentials against the LDAP.</p>", "description": "<p>Verify credentials against the LDAP.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "Authentication", "groupTitle": "Authentication",
"header": { "header": {
"fields": { "fields": {
@ -169,7 +169,7 @@
"group": "PasswordReset", "group": "PasswordReset",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Start password reset request.</p>", "description": "<p>Start password reset request.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "PasswordReset", "groupTitle": "PasswordReset",
"header": { "header": {
"fields": { "fields": {
@ -240,7 +240,7 @@
"group": "PasswordReset", "group": "PasswordReset",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Serve a page that requires the username.</p>", "description": "<p>Serve a page that requires the username.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "PasswordReset", "groupTitle": "PasswordReset",
"header": { "header": {
"fields": { "fields": {
@ -277,7 +277,7 @@
} }
}, },
"description": "<p>Set a new password for the user.</p>", "description": "<p>Set a new password for the user.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "PasswordReset", "groupTitle": "PasswordReset",
"header": { "header": {
"fields": { "fields": {
@ -301,7 +301,7 @@
"group": "PasswordReset", "group": "PasswordReset",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Start password reset request.</p>", "description": "<p>Start password reset request.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "PasswordReset", "groupTitle": "PasswordReset",
"header": { "header": {
"fields": { "fields": {
@ -366,7 +366,7 @@
"group": "TOTP", "group": "TOTP",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Serves the TOTP registration page that displays the secret. The secret is a QRCode and a base32 secret.</p>", "description": "<p>Serves the TOTP registration page that displays the secret. The secret is a QRCode and a base32 secret.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "TOTP", "groupTitle": "TOTP",
"header": { "header": {
"fields": { "fields": {
@ -437,7 +437,7 @@
"group": "TOTP", "group": "TOTP",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Initiates the identity validation</p>", "description": "<p>Initiates the identity validation</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "TOTP", "groupTitle": "TOTP",
"header": { "header": {
"fields": { "fields": {
@ -521,7 +521,7 @@
"group": "Success 302", "group": "Success 302",
"optional": false, "optional": false,
"field": "Redirect", "field": "Redirect",
"description": "<p>to the URL that has been stored during last call to /verify.</p>" "description": "<p>to the URL that has been stored during last call to /api/verify.</p>"
} }
] ]
} }
@ -549,7 +549,7 @@
} }
}, },
"description": "<p>Verify TOTP token. The user is authenticated upon success.</p>", "description": "<p>Verify TOTP token. The user is authenticated upon success.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "TOTP", "groupTitle": "TOTP",
"header": { "header": {
"fields": { "fields": {
@ -579,7 +579,7 @@
"group": "Success 302", "group": "Success 302",
"optional": false, "optional": false,
"field": "Redirect", "field": "Redirect",
"description": "<p>to the URL that has been stored during last call to /verify.</p>" "description": "<p>to the URL that has been stored during last call to /api/verify.</p>"
} }
] ]
} }
@ -607,7 +607,7 @@
} }
}, },
"description": "<p>Complete authentication request of the U2F device.</p>", "description": "<p>Complete authentication request of the U2F device.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -637,13 +637,13 @@
"group": "Success 302", "group": "Success 302",
"optional": false, "optional": false,
"field": "Redirect", "field": "Redirect",
"description": "<p>to the URL that has been stored during last call to /verify.</p>" "description": "<p>to the URL that has been stored during last call to /api/verify.</p>"
} }
] ]
} }
}, },
"description": "<p>Complete U2F registration request.</p>", "description": "<p>Complete U2F registration request.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -679,7 +679,7 @@
"name": "RequestU2FRegistration", "name": "RequestU2FRegistration",
"group": "U2F", "group": "U2F",
"version": "1.0.0", "version": "1.0.0",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -745,7 +745,7 @@
"group": "U2F", "group": "U2F",
"version": "1.0.0", "version": "1.0.0",
"description": "<p>Serves the U2F registration page that asks the user to touch the token of the U2F device.</p>", "description": "<p>Serves the U2F registration page that asks the user to touch the token of the U2F device.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -850,7 +850,7 @@
} }
}, },
"description": "<p>Initiate an authentication request using a U2F device.</p>", "description": "<p>Initiate an authentication request using a U2F device.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -908,7 +908,7 @@
} }
}, },
"description": "<p>Initiate a U2F device registration request.</p>", "description": "<p>Initiate a U2F device registration request.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "U2F", "groupTitle": "U2F",
"header": { "header": {
"fields": { "fields": {
@ -926,11 +926,24 @@
}, },
{ {
"type": "get", "type": "get",
"url": "/verify", "url": "/api/verify",
"title": "Verify user authentication", "title": "Verify user authentication",
"name": "VerifyAuthentication", "name": "VerifyAuthentication",
"group": "Verification", "group": "Verification",
"version": "1.0.0", "version": "1.0.0",
"parameter": {
"fields": {
"Parameter": [
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "redirect",
"description": "<p>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.</p>"
}
]
}
},
"success": { "success": {
"fields": { "fields": {
"Success 204": [ "Success 204": [
@ -945,18 +958,26 @@
}, },
"error": { "error": {
"fields": { "fields": {
"Error 302": [
{
"group": "Error 302",
"optional": false,
"field": "redirect",
"description": "<p>The user is redirected if redirect parameter is provided.</p>"
}
],
"Error 401": [ "Error 401": [
{ {
"group": "Error 401", "group": "Error 401",
"optional": false, "optional": false,
"field": "status", "field": "status",
"description": "<p>The user is not authenticated.</p>" "description": "<p>The user get an error if access failed</p>"
} }
] ]
} }
}, },
"description": "<p>Verify that the user is authenticated, i.e., the two factors have been validated</p>", "description": "<p>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.</p>",
"filename": "src/server/endpoints.ts", "filename": "shared/api.ts",
"groupTitle": "Verification", "groupTitle": "Verification",
"header": { "header": {
"fields": { "fields": {

View File

@ -1,14 +1,14 @@
define({ define({
"title": "Authelia API documentation", "title": "Authelia API documentation",
"name": "authelia", "name": "authelia",
"version": "2.1.3", "version": "3.7.0",
"description": "2FA Single Sign-On server for nginx using LDAP, TOTP and U2F", "description": "2FA Single Sign-On server for nginx using LDAP, TOTP and U2F",
"sampleUrl": false, "sampleUrl": false,
"defaultVersion": "0.0.0", "defaultVersion": "0.0.0",
"apidoc": "0.3.0", "apidoc": "0.3.0",
"generator": { "generator": {
"name": "apidoc", "name": "apidoc",
"time": "2017-06-11T20:41:36.025Z", "time": "2017-12-04T21:38:44.927Z",
"url": "http://apidocjs.com", "url": "http://apidocjs.com",
"version": "0.17.6" "version": "0.17.6"
} }

View File

@ -1,14 +1,14 @@
{ {
"title": "Authelia API documentation", "title": "Authelia API documentation",
"name": "authelia", "name": "authelia",
"version": "2.1.3", "version": "3.7.0",
"description": "2FA Single Sign-On server for nginx using LDAP, TOTP and U2F", "description": "2FA Single Sign-On server for nginx using LDAP, TOTP and U2F",
"sampleUrl": false, "sampleUrl": false,
"defaultVersion": "0.0.0", "defaultVersion": "0.0.0",
"apidoc": "0.3.0", "apidoc": "0.3.0",
"generator": { "generator": {
"name": "apidoc", "name": "apidoc",
"time": "2017-06-11T20:41:36.025Z", "time": "2017-12-04T21:38:44.927Z",
"url": "http://apidocjs.com", "url": "http://apidocjs.com",
"version": "0.17.6" "version": "0.17.6"
} }

View File

@ -322,5 +322,25 @@ http {
proxy_pass $upstream_headers; 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;
}
}
} }

View File

@ -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, export function replyWithError400(req: express.Request,
res: express.Response, logger: IRequestLogger) { res: express.Response, logger: IRequestLogger) {
return replyWithError(req, res, 400, logger); return replyWithError(req, res, 400, logger);

View File

@ -5,6 +5,7 @@ import ErrorReplies = require("../../ErrorReplies");
import { ServerVariables } from "../../ServerVariables"; import { ServerVariables } from "../../ServerVariables";
import GetWithSessionCookieMethod from "./get_session_cookie"; import GetWithSessionCookieMethod from "./get_session_cookie";
import GetWithBasicAuthMethod from "./get_basic_auth"; import GetWithBasicAuthMethod from "./get_basic_auth";
import Constants = require("../../../../../shared/constants");
import { AuthenticationSessionHandler } import { AuthenticationSessionHandler }
from "../../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) { export default function (vars: ServerVariables) {
return function (req: Express.Request, res: Express.Response) return function (req: Express.Request, res: Express.Response)
: BluebirdPromise<void> { : BluebirdPromise<void> {
@ -66,7 +73,15 @@ export default function (vars: ServerVariables) {
.catch(Exceptions.DomainAccessDenied, ErrorReplies .catch(Exceptions.DomainAccessDenied, ErrorReplies
.replyWithError403(req, res, vars.logger)) .replyWithError403(req, res, vars.logger))
// The user is not yet authenticated -> 401 // 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);
}
});
}; };
} }

View File

@ -24,11 +24,11 @@ describe("test /api/verify endpoint", function () {
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
req.originalUrl = "/api/xxxx"; req.originalUrl = "/api/xxxx";
req.query = { req.query = {
redirect: "http://redirect.url" redirect: "undefined"
}; };
AuthenticationSessionHandler.reset(req as any); AuthenticationSessionHandler.reset(req as any);
req.headers.host = "secret.example.com"; req.headers.host = "secret.example.com";
const s = ServerVariablesMockBuilder.build(true); const s = ServerVariablesMockBuilder.build(false);
mocks = s.mocks; mocks = s.mocks;
vars = s.variables; vars = s.variables;
authSession = AuthenticationSessionHandler.get(req as any, vars.logger); 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 () { describe("given user tries to access a single factor endpoint", function () {
beforeEach(function () { beforeEach(function () {
req.query = {
redirect: "http://redirect.url"
};
req.headers["host"] = "redirect.url"; req.headers["host"] = "redirect.url";
mocks.config.authentication_methods.per_subdomain_methods = { mocks.config.authentication_methods.per_subdomain_methods = {
"redirect.url": "single_factor" "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 () { describe("with basic auth", function () {
it("should authenticate correctly", function () { it("should authenticate correctly", function () {
mocks.accessController.isAccessAllowedMock.returns(true); mocks.accessController.isAccessAllowedMock.returns(true);

View File

@ -39,7 +39,7 @@
* @apiUse UserSession * @apiUse UserSession
* @apiUse InternalError * @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. * @apiDescription Complete U2F registration request.
*/ */
@ -68,7 +68,7 @@ export const SECOND_FACTOR_U2F_REGISTER_REQUEST_GET = "/api/u2f/register_request
* @apiUse UserSession * @apiUse UserSession
* @apiUse InternalError * @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. * @apiError (Error 403) {none} error No authentication request has been provided.
* *
* @apiDescription Complete authentication request of the U2F device. * @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. * @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. * @apiError (Error 401) {none} error TOTP token is invalid.
* *
* @apiDescription Verify TOTP token. The user is authenticated upon success. * @apiDescription Verify TOTP token. The user is authenticated upon success.
@ -270,8 +270,13 @@ export const SECOND_FACTOR_GET = "/secondfactor";
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiUse UserSession * @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. * @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 * @apiDescription Verify that the user is authenticated, i.e., the two
* factors have been validated. * factors have been validated.

View File

@ -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"

View File

@ -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"));
}
});
})
});