mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge pull request #2953 from QuantumEntangledAndy/feat/AdditionalLogins
Add additional login methods
This commit is contained in:
@@ -2,14 +2,19 @@
|
||||
* When applied, this middleware will ensure the request contains the required header for basic authentication and only
|
||||
* allow access to the endpoint after successful authentication.
|
||||
*/
|
||||
const { getConfig } = require('../util.js');
|
||||
const { getAllUserHandles, toKey, getPasswordHash } = require('../users.js');
|
||||
const { getConfig, getConfigValue } = require('../util.js');
|
||||
const storage = require('node-persist');
|
||||
|
||||
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
|
||||
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
||||
|
||||
const unauthorizedResponse = (res) => {
|
||||
res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"');
|
||||
return res.status(401).send('Authentication required');
|
||||
};
|
||||
|
||||
const basicAuthMiddleware = function (request, response, callback) {
|
||||
const basicAuthMiddleware = async function (request, response, callback) {
|
||||
const config = getConfig();
|
||||
const authHeader = request.headers.authorization;
|
||||
|
||||
@@ -23,15 +28,25 @@ const basicAuthMiddleware = function (request, response, callback) {
|
||||
return unauthorizedResponse(response);
|
||||
}
|
||||
|
||||
const usePerUserAuth = PER_USER_BASIC_AUTH && ENABLE_ACCOUNTS;
|
||||
const [username, password] = Buffer.from(credentials, 'base64')
|
||||
.toString('utf8')
|
||||
.split(':');
|
||||
|
||||
if (username === config.basicAuthUser.username && password === config.basicAuthUser.password) {
|
||||
if (!usePerUserAuth && username === config.basicAuthUser.username && password === config.basicAuthUser.password) {
|
||||
return callback();
|
||||
} else {
|
||||
return unauthorizedResponse(response);
|
||||
} else if (usePerUserAuth) {
|
||||
const userHandles = await getAllUserHandles();
|
||||
for (const userHandle of userHandles) {
|
||||
if (username === userHandle) {
|
||||
const user = await storage.getItem(toKey(userHandle));
|
||||
if (user && user.enabled && (user.password && user.password === getPasswordHash(password, user.salt))) {
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return unauthorizedResponse(response);
|
||||
};
|
||||
|
||||
module.exports = basicAuthMiddleware;
|
||||
|
104
src/users.js
104
src/users.js
@@ -19,6 +19,8 @@ const { readSecret, writeSecret } = require('./endpoints/secrets');
|
||||
const KEY_PREFIX = 'user:';
|
||||
const AVATAR_PREFIX = 'avatar:';
|
||||
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
||||
const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false);
|
||||
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
|
||||
const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
|
||||
|
||||
/**
|
||||
@@ -565,13 +567,42 @@ function shouldRedirectToLogin(request) {
|
||||
return ENABLE_ACCOUNTS && !request.user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries auto-login if there is only one user and it's not password protected.
|
||||
* or another configured method such authlia or basic
|
||||
* @param {import('express').Request} request Request object
|
||||
* @param {boolean} basicAuthMode If Basic auth mode is enabled
|
||||
* @returns {Promise<boolean>} Whether auto-login was performed
|
||||
*/
|
||||
async function tryAutoLogin(request, basicAuthMode) {
|
||||
if (!ENABLE_ACCOUNTS || request.user || !request.session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!request.query.noauto) {
|
||||
if (await singleUserLogin(request)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (AUTHELIA_AUTH && await autheliaUserLogin(request)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (basicAuthMode && PER_USER_BASIC_AUTH && await basicUserLogin(request)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries auto-login if there is only one user and it's not password protected.
|
||||
* @param {import('express').Request} request Request object
|
||||
* @returns {Promise<boolean>} Whether auto-login was performed
|
||||
*/
|
||||
async function tryAutoLogin(request) {
|
||||
if (!ENABLE_ACCOUNTS || request.user || !request.session) {
|
||||
async function singleUserLogin(request) {
|
||||
if (!request.session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -583,6 +614,75 @@ async function tryAutoLogin(request) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries auto-login with authlia trusted headers.
|
||||
* https://www.authelia.com/integration/trusted-header-sso/introduction/
|
||||
* @param {import('express').Request} request Request object
|
||||
* @returns {Promise<boolean>} Whether auto-login was performed
|
||||
*/
|
||||
async function autheliaUserLogin(request) {
|
||||
if (!request.session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const remoteUser = request.get('Remote-User');
|
||||
if (!remoteUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userHandles = await getAllUserHandles();
|
||||
for (const userHandle of userHandles) {
|
||||
if (remoteUser === userHandle) {
|
||||
const user = await storage.getItem(toKey(userHandle));
|
||||
if (user && user.enabled) {
|
||||
request.session.handle = userHandle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries auto-login with basic auth username.
|
||||
* @param {import('express').Request} request Request object
|
||||
* @returns {Promise<boolean>} Whether auto-login was performed
|
||||
*/
|
||||
async function basicUserLogin(request) {
|
||||
if (!request.session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const authHeader = request.headers.authorization;
|
||||
|
||||
if (!authHeader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [scheme, credentials] = authHeader.split(' ');
|
||||
|
||||
if (scheme !== 'Basic' || !credentials) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [username, password] = Buffer.from(credentials, 'base64')
|
||||
.toString('utf8')
|
||||
.split(':');
|
||||
|
||||
const userHandles = await getAllUserHandles();
|
||||
for (const userHandle of userHandles) {
|
||||
if (username === userHandle) {
|
||||
const user = await storage.getItem(toKey(userHandle));
|
||||
// Verify pass again here just to be sure
|
||||
if (user && user.enabled && user.password && user.password === getPasswordHash(password, user.salt)) {
|
||||
request.session.handle = userHandle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
Reference in New Issue
Block a user