Authentik automatic login support (#4600) * feat: authentik auto login * refactor: move SSO login configuration under sso section in config * Migrate SSO config keys read --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

e68f9e482e2fab8eeea3b5b7372fca2d6c75da98

Jakub Jędrzejczyk <aegiruchan@gmail.com>

Signed
3 files changed, +55 -7Showing whitespace changes
default/config.yaml+16 -4
@@ -82,6 +82,13 @@ requestProxy:
82enableUserAccounts: false82enableUserAccounts: false
83# Enable discreet login mode: hides user list on the login screen83# Enable discreet login mode: hides user list on the login screen
84enableDiscreetLogin: false84enableDiscreetLogin: false
85# If `basicAuthMode` and this are enabled then
86# the username and passwords for basic auth are the same as those
87# for the individual accounts
88perUserBasicAuth: false
89
90# -- SSO LOGIN CONFIGURATION --
91sso:
85 # Enable's authlia based auto login. Only enable this if you92 # Enable's authlia based auto login. Only enable this if you
86 # have setup and installed Authelia as a middle-ware on your93 # have setup and installed Authelia as a middle-ware on your
87 # reverse proxy94 # reverse proxy
@@ -90,10 +97,15 @@ enableDiscreetLogin: false
90 # as that used for authlia. (Ensure the username in authlia97 # as that used for authlia. (Ensure the username in authlia
91 # is an exact match in lowercase with that in sillytavern)98 # is an exact match in lowercase with that in sillytavern)
92 autheliaAuth: false99 autheliaAuth: false
93# If `basicAuthMode` and this are enabled then100 # Enable's authentik based auto login. Only enable this if you
94# the username and passwords for basic auth are the same as those101 # have setup and installed Authentik as a middle-ware on your
95# for the individual accounts102 # reverse proxy.
96perUserBasicAuth: false103 # https://goauthentik.io/
104 # This will use auto login to an account with the same username
105 # as that used for authentik. (Ensure the username in authentik
106 # is an exact match in lowercase with that in sillytavern).
107 authentikAuth: false
108
97# Host whitelist configuration. Recommended if you're using a listen mode109# Host whitelist configuration. Recommended if you're using a listen mode
98hostWhitelist:110hostWhitelist:
99 # Enable or disable host whitelisting111 # Enable or disable host whitelisting
src/config-init.js+10 -0
@@ -124,6 +124,16 @@ const keyMigrationMap = [
124 migrate: () => void 0,124 migrate: () => void 0,
125 remove: true,125 remove: true,
126 },126 },
127 {
128 oldKey: 'autheliaAuth',
129 newKey: 'sso.autheliaAuth',
130 migrate: (value) => value,
131 },
132 {
133 oldKey: 'authentikAuth',
134 newKey: 'sso.authentikAuth',
135 migrate: (value) => value,
136 },
127];137];
128138
129/**139/**
src/users.js+29 -3
@@ -24,7 +24,8 @@ import { serverDirectory } from './server-directory.js';
24export const KEY_PREFIX = 'user:';24export const KEY_PREFIX = 'user:';
25const AVATAR_PREFIX = 'avatar:';25const AVATAR_PREFIX = 'avatar:';
26const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false, 'boolean');26const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false, 'boolean');
27const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false, 'boolean');27const AUTHELIA_AUTH = getConfigValue('sso.autheliaAuth', false, 'boolean');
28const AUTHENTIK_AUTH = getConfigValue('sso.authentikAuth', false, 'boolean');
28const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false, 'boolean');29const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false, 'boolean');
29const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');30const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
3031
@@ -716,6 +717,10 @@ export async function tryAutoLogin(request, basicAuthMode) {
716 return true;717 return true;
717 }718 }
718719
720 if (AUTHENTIK_AUTH && await authentikUserLogin(request)) {
721 return true;
722 }
723
719 if (basicAuthMode && PER_USER_BASIC_AUTH && await basicUserLogin(request)) {724 if (basicAuthMode && PER_USER_BASIC_AUTH && await basicUserLogin(request)) {
720 return true;725 return true;
721 }726 }
@@ -746,20 +751,41 @@ async function singleUserLogin(request) {
746}751}
747752
748/**753/**
749 * Tries auto-login with authlia trusted headers.754 * Attempts auto-login using an Authelia header.
750 * https://www.authelia.com/integration/trusted-header-sso/introduction/755 * https://www.authelia.com/integration/trusted-header-sso/introduction/
751 * @param {import('express').Request} request Request object756 * @param {import('express').Request} request Request object
752 * @returns {Promise<boolean>} Whether auto-login was performed757 * @returns {Promise<boolean>} Whether auto-login was performed
753 */758 */
754async function autheliaUserLogin(request) {759async function autheliaUserLogin(request) {
760 return headerUserLogin(request, 'Remote-User');
761}
762
763/**
764 * Attempts auto-login using an Authentik header.
765 * https://docs.goauthentik.io/add-secure-apps/providers/proxy/forward_auth/
766 * @param {import('express').Request} request Request object
767 * @returns {Promise<boolean>} Whether auto-login was performed
768 */
769async function authentikUserLogin(request) {
770 return headerUserLogin(request, 'X-Authentik-Username');
771}
772
773/**
774 * Tries auto-login with a given header.
775 * @param {import('express').Request} request Request object
776 * @param {string} [header='Remote-User'] The header to use for the trusted user
777 * @returns {Promise<boolean>} Whether auto-login was performed
778 */
779async function headerUserLogin(request, header = 'Remote-User') {
755 if (!request.session) {780 if (!request.session) {
756 return false;781 return false;
757 }782 }
758783
759 const remoteUser = request.get('Remote-User');784 const remoteUser = request.get(header);
760 if (!remoteUser) {785 if (!remoteUser) {
761 return false;786 return false;
762 }787 }
788 console.debug(`Attempting auto-login for user from header ${header}: ${remoteUser}`);
763789
764 const userHandles = await getAllUserHandles();790 const userHandles = await getAllUserHandles();
765 for (const userHandle of userHandles) {791 for (const userHandle of userHandles) {