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>
Signed| @@ -82,18 +82,30 @@ requestProxy: | |||
| 82 | enableUserAccounts: false | 82 | enableUserAccounts: false |
| 83 | # Enable discreet login mode: hides user list on the login screen | 83 | # Enable discreet login mode: hides user list on the login screen |
| 84 | enableDiscreetLogin: false | 84 | enableDiscreetLogin: false |
| 85 | # Enable's authlia based auto login. Only enable this if you | ||
| 86 | # have setup and installed Authelia as a middle-ware on your | ||
| 87 | # reverse proxy | ||
| 88 | # https://www.authelia.com/ | ||
| 89 | # This will use auto login to an account with the same username | ||
| 90 | # as that used for authlia. (Ensure the username in authlia | ||
| 91 | # is an exact match in lowercase with that in sillytavern) | ||
| 92 | autheliaAuth: false | ||
| 93 | # If `basicAuthMode` and this are enabled then | 85 | # If `basicAuthMode` and this are enabled then |
| 94 | # the username and passwords for basic auth are the same as those | 86 | # the username and passwords for basic auth are the same as those |
| 95 | # for the individual accounts | 87 | # for the individual accounts |
| 96 | perUserBasicAuth: false | 88 | perUserBasicAuth: false |
| 89 | |||
| 90 | # -- SSO LOGIN CONFIGURATION -- | ||
| 91 | sso: | ||
| 92 | # Enable's authlia based auto login. Only enable this if you | ||
| 93 | # have setup and installed Authelia as a middle-ware on your | ||
| 94 | # reverse proxy | ||
| 95 | # https://www.authelia.com/ | ||
| 96 | # This will use auto login to an account with the same username | ||
| 97 | # as that used for authlia. (Ensure the username in authlia | ||
| 98 | # is an exact match in lowercase with that in sillytavern) | ||
| 99 | autheliaAuth: false | ||
| 100 | # Enable's authentik based auto login. Only enable this if you | ||
| 101 | # have setup and installed Authentik as a middle-ware on your | ||
| 102 | # reverse proxy. | ||
| 103 | # 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 mode | 109 | # Host whitelist configuration. Recommended if you're using a listen mode |
| 98 | hostWhitelist: | 110 | hostWhitelist: |
| 99 | # Enable or disable host whitelisting | 111 | # Enable or disable host whitelisting |
| @@ -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 | ]; |
| 128 | 138 | ||
| 129 | /** | 139 | /** |
| @@ -24,7 +24,8 @@ import { serverDirectory } from './server-directory.js'; | |||
| 24 | export const KEY_PREFIX = 'user:'; | 24 | export const KEY_PREFIX = 'user:'; |
| 25 | const AVATAR_PREFIX = 'avatar:'; | 25 | const AVATAR_PREFIX = 'avatar:'; |
| 26 | const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false, 'boolean'); | 26 | const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false, 'boolean'); |
| 27 | const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false, 'boolean'); | 27 | const AUTHELIA_AUTH = getConfigValue('sso.autheliaAuth', false, 'boolean'); |
| 28 | const AUTHENTIK_AUTH = getConfigValue('sso.authentikAuth', false, 'boolean'); | ||
| 28 | const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false, 'boolean'); | 29 | const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false, 'boolean'); |
| 29 | const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64'); | 30 | const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64'); |
| 30 | 31 | ||
| @@ -716,6 +717,10 @@ export async function tryAutoLogin(request, basicAuthMode) { | |||
| 716 | return true; | 717 | return true; |
| 717 | } | 718 | } |
| 718 | 719 | ||
| 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 | } |
| 747 | 752 | ||
| 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 object | 756 | * @param {import('express').Request} request Request object |
| 752 | * @returns {Promise<boolean>} Whether auto-login was performed | 757 | * @returns {Promise<boolean>} Whether auto-login was performed |
| 753 | */ | 758 | */ |
| 754 | async function autheliaUserLogin(request) { | 759 | async 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 | */ | ||
| 769 | async 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 | */ | ||
| 779 | async function headerUserLogin(request, header = 'Remote-User') { | ||
| 755 | if (!request.session) { | 780 | if (!request.session) { |
| 756 | return false; | 781 | return false; |
| 757 | } | 782 | } |
| 758 | 783 | ||
| 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}`); | ||
| 763 | 789 | ||
| 764 | const userHandles = await getAllUserHandles(); | 790 | const userHandles = await getAllUserHandles(); |
| 765 | for (const userHandle of userHandles) { | 791 | for (const userHandle of userHandles) { |