mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
Flag to disable login UI and endpoints for credentialed auth (#3984)
* Flag to disable login UI and endpoints for credentialed auth * dev build * fix translation key
This commit is contained in:
2
.github/workflows/dev-build.yaml
vendored
2
.github/workflows/dev-build.yaml
vendored
@@ -6,7 +6,7 @@ concurrency:
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['2095-model-swap-in-chat'] # put your current branch to create a build. Core team only.
|
||||
branches: ['3982-disable-login-simple-sso'] # put your current branch to create a build. Core team only.
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'cloud-deployments/*'
|
||||
|
||||
@@ -329,6 +329,7 @@ GID='1000'
|
||||
# Enable simple SSO passthrough to pre-authenticate users from a third party service.
|
||||
# See https://docs.anythingllm.com/configuration#simple-sso-passthrough for more information.
|
||||
# SIMPLE_SSO_ENABLED=1
|
||||
# SIMPLE_SSO_NO_LOGIN=1
|
||||
|
||||
# Allow scraping of any IP address in collector - must be string "true" to be enabled
|
||||
# See https://docs.anythingllm.com/configuration#local-ip-address-scraping for more information.
|
||||
|
||||
33
frontend/src/hooks/useSimpleSSO.js
Normal file
33
frontend/src/hooks/useSimpleSSO.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import System from "@/models/system";
|
||||
|
||||
/**
|
||||
* Checks if Simple SSO is enabled and if the user should be redirected to the SSO login page.
|
||||
* @returns {{loading: boolean, ssoConfig: {enabled: boolean, noLogin: boolean}}}
|
||||
*/
|
||||
export default function useSimpleSSO() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [ssoConfig, setSsoConfig] = useState({
|
||||
enabled: false,
|
||||
noLogin: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function checkSsoConfig() {
|
||||
try {
|
||||
const settings = await System.keys();
|
||||
setSsoConfig({
|
||||
enabled: settings?.SimpleSSOEnabled,
|
||||
noLogin: settings?.SimpleSSONoLogin,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
checkSsoConfig();
|
||||
}, []);
|
||||
|
||||
return { loading, ssoConfig };
|
||||
}
|
||||
@@ -39,9 +39,9 @@ export default function SimpleSSOPassthrough() {
|
||||
|
||||
if (error)
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex items-center justify-center flex-col gap-4">
|
||||
<p className="text-white font-mono text-lg">{error}</p>
|
||||
<p className="text-white/80 font-mono text-sm">
|
||||
<div className="w-screen h-screen overflow-hidden bg-theme-bg-primary flex items-center justify-center flex-col gap-4">
|
||||
<p className="text-theme-text-primary font-mono text-lg">{error}</p>
|
||||
<p className="text-theme-text-secondary font-mono text-sm">
|
||||
Please contact the system administrator about this error.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -4,11 +4,24 @@ import { FullScreenLoader } from "@/components/Preloader";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import paths from "@/utils/paths";
|
||||
import useQuery from "@/hooks/useQuery";
|
||||
import useSimpleSSO from "@/hooks/useSimpleSSO";
|
||||
|
||||
/**
|
||||
* Login page that handles both single and multi-user login.
|
||||
*
|
||||
* If Simple SSO is enabled and no login is allowed, the user will be redirected to the SSO login page
|
||||
* which may not have a token so the login will fail.
|
||||
*
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export default function Login() {
|
||||
const query = useQuery();
|
||||
const { loading: ssoLoading, ssoConfig } = useSimpleSSO();
|
||||
const { loading, requiresAuth, mode } = usePasswordModal(!!query.get("nt"));
|
||||
if (loading) return <FullScreenLoader />;
|
||||
|
||||
if (loading || ssoLoading) return <FullScreenLoader />;
|
||||
if (ssoConfig.enabled && ssoConfig.noLogin)
|
||||
return <Navigate to={paths.sso.login()} />;
|
||||
if (requiresAuth === false) return <Navigate to={paths.home()} />;
|
||||
|
||||
return <PasswordModal mode={mode} />;
|
||||
|
||||
@@ -284,7 +284,7 @@ const MyTeam = ({ setMultiUserLoginValid, myTeamSubmitRef, navigate }) => {
|
||||
htmlFor="name"
|
||||
className="block mb-3 text-sm font-medium text-white"
|
||||
>
|
||||
{t("common.adminUsername")}
|
||||
{t("onboarding.userSetup.adminUsername")}
|
||||
</label>
|
||||
<input
|
||||
name="username"
|
||||
|
||||
@@ -18,6 +18,11 @@ export default {
|
||||
login: (noTry = false) => {
|
||||
return `/login${noTry ? "?nt=1" : ""}`;
|
||||
},
|
||||
sso: {
|
||||
login: () => {
|
||||
return "/sso/simple";
|
||||
},
|
||||
},
|
||||
onboarding: {
|
||||
home: () => {
|
||||
return "/onboarding";
|
||||
|
||||
@@ -326,6 +326,7 @@ TTS_PROVIDER="native"
|
||||
# Enable simple SSO passthrough to pre-authenticate users from a third party service.
|
||||
# See https://docs.anythingllm.com/configuration#simple-sso-passthrough for more information.
|
||||
# SIMPLE_SSO_ENABLED=1
|
||||
# SIMPLE_SSO_NO_LOGIN=1
|
||||
|
||||
# Allow scraping of any IP address in collector - must be string "true" to be enabled
|
||||
# See https://docs.anythingllm.com/configuration#local-ip-address-scraping for more information.
|
||||
|
||||
@@ -25,6 +25,9 @@ const {
|
||||
} = require("../utils/middleware/multiUserProtected");
|
||||
const { validatedRequest } = require("../utils/middleware/validatedRequest");
|
||||
const ImportedPlugin = require("../utils/agents/imported");
|
||||
const {
|
||||
simpleSSOLoginDisabledMiddleware,
|
||||
} = require("../utils/middleware/simpleSSOEnabled");
|
||||
|
||||
function adminEndpoints(app) {
|
||||
if (!app) return;
|
||||
@@ -168,7 +171,11 @@ function adminEndpoints(app) {
|
||||
|
||||
app.post(
|
||||
"/admin/invite/new",
|
||||
[validatedRequest, strictMultiUserRoleValid([ROLES.admin, ROLES.manager])],
|
||||
[
|
||||
validatedRequest,
|
||||
strictMultiUserRoleValid([ROLES.admin, ROLES.manager]),
|
||||
simpleSSOLoginDisabledMiddleware,
|
||||
],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const user = await userFromSession(request, response);
|
||||
|
||||
@@ -2,6 +2,9 @@ const { EventLogs } = require("../models/eventLogs");
|
||||
const { Invite } = require("../models/invite");
|
||||
const { User } = require("../models/user");
|
||||
const { reqBody } = require("../utils/http");
|
||||
const {
|
||||
simpleSSOLoginDisabledMiddleware,
|
||||
} = require("../utils/middleware/simpleSSOEnabled");
|
||||
|
||||
function inviteEndpoints(app) {
|
||||
if (!app) return;
|
||||
@@ -31,46 +34,50 @@ function inviteEndpoints(app) {
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/invite/:code", async (request, response) => {
|
||||
try {
|
||||
const { code } = request.params;
|
||||
const { username, password } = reqBody(request);
|
||||
const invite = await Invite.get({ code });
|
||||
if (!invite || invite.status !== "pending") {
|
||||
response
|
||||
.status(200)
|
||||
.json({ success: false, error: "Invite not found or is invalid." });
|
||||
return;
|
||||
app.post(
|
||||
"/invite/:code",
|
||||
[simpleSSOLoginDisabledMiddleware],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { code } = request.params;
|
||||
const { username, password } = reqBody(request);
|
||||
const invite = await Invite.get({ code });
|
||||
if (!invite || invite.status !== "pending") {
|
||||
response
|
||||
.status(200)
|
||||
.json({ success: false, error: "Invite not found or is invalid." });
|
||||
return;
|
||||
}
|
||||
|
||||
const { user, error } = await User.create({
|
||||
username,
|
||||
password,
|
||||
role: "default",
|
||||
});
|
||||
if (!user) {
|
||||
console.error("Accepting invite:", error);
|
||||
response
|
||||
.status(200)
|
||||
.json({ success: false, error: "Could not create user." });
|
||||
return;
|
||||
}
|
||||
|
||||
await Invite.markClaimed(invite.id, user);
|
||||
await EventLogs.logEvent(
|
||||
"invite_accepted",
|
||||
{
|
||||
username: user.username,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
response.status(200).json({ success: true, error: null });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
response.sendStatus(500).end();
|
||||
}
|
||||
|
||||
const { user, error } = await User.create({
|
||||
username,
|
||||
password,
|
||||
role: "default",
|
||||
});
|
||||
if (!user) {
|
||||
console.error("Accepting invite:", error);
|
||||
response
|
||||
.status(200)
|
||||
.json({ success: false, error: "Could not create user." });
|
||||
return;
|
||||
}
|
||||
|
||||
await Invite.markClaimed(invite.id, user);
|
||||
await EventLogs.logEvent(
|
||||
"invite_accepted",
|
||||
{
|
||||
username: user.username,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
response.status(200).json({ success: true, error: null });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
response.sendStatus(500).end();
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { inviteEndpoints };
|
||||
|
||||
@@ -54,7 +54,10 @@ const { BrowserExtensionApiKey } = require("../models/browserExtensionApiKey");
|
||||
const {
|
||||
chatHistoryViewable,
|
||||
} = require("../utils/middleware/chatHistoryViewable");
|
||||
const { simpleSSOEnabled } = require("../utils/middleware/simpleSSOEnabled");
|
||||
const {
|
||||
simpleSSOEnabled,
|
||||
simpleSSOLoginDisabled,
|
||||
} = require("../utils/middleware/simpleSSOEnabled");
|
||||
const { TemporaryAuthToken } = require("../models/temporaryAuthToken");
|
||||
const { SystemPromptVariables } = require("../models/systemPromptVariables");
|
||||
const { VALID_COMMANDS } = require("../utils/chats");
|
||||
@@ -116,6 +119,17 @@ function systemEndpoints(app) {
|
||||
const bcrypt = require("bcrypt");
|
||||
|
||||
if (await SystemSettings.isMultiUserMode()) {
|
||||
if (simpleSSOLoginDisabled()) {
|
||||
response.status(403).json({
|
||||
user: null,
|
||||
valid: false,
|
||||
token: null,
|
||||
message:
|
||||
"[005] Login via credentials has been disabled by the administrator.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { username, password } = reqBody(request);
|
||||
const existingUser = await User._get({ username: String(username) });
|
||||
|
||||
|
||||
@@ -279,6 +279,12 @@ const SystemSettings = {
|
||||
// Disable View Chat History for the whole instance.
|
||||
DisableViewChatHistory:
|
||||
"DISABLE_VIEW_CHAT_HISTORY" in process.env || false,
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Simple SSO Settings
|
||||
// --------------------------------------------------------
|
||||
SimpleSSOEnabled: "SIMPLE_SSO_ENABLED" in process.env || false,
|
||||
SimpleSSONoLogin: "SIMPLE_SSO_NO_LOGIN" in process.env || false,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -1084,6 +1084,7 @@ function dumpENV() {
|
||||
"DISABLE_VIEW_CHAT_HISTORY",
|
||||
// Simple SSO
|
||||
"SIMPLE_SSO_ENABLED",
|
||||
"SIMPLE_SSO_NO_LOGIN",
|
||||
// Community Hub
|
||||
"COMMUNITY_HUB_BUNDLE_DOWNLOADS_ENABLED",
|
||||
|
||||
|
||||
@@ -34,6 +34,51 @@ async function simpleSSOEnabled(_, response, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if simple SSO login is disabled by checking if the
|
||||
* SIMPLE_SSO_NO_LOGIN environment variable is set as well as
|
||||
* SIMPLE_SSO_ENABLED is set.
|
||||
*
|
||||
* This check should only be run when in multi-user mode when used.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function simpleSSOLoginDisabled() {
|
||||
return (
|
||||
"SIMPLE_SSO_ENABLED" in process.env && "SIMPLE_SSO_NO_LOGIN" in process.env
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that checks if simple SSO login is disabled by checking if the
|
||||
* SIMPLE_SSO_NO_LOGIN environment variable is set as well as
|
||||
* SIMPLE_SSO_ENABLED is set.
|
||||
*
|
||||
* This middleware will 403 if SSO is enabled and no login is allowed and
|
||||
* the system is in multi-user mode. Otherwise, it will call next.
|
||||
*
|
||||
* @param {import("express").Request} request
|
||||
* @param {import("express").Response} response
|
||||
* @param {import("express").NextFunction} next
|
||||
* @returns {void}
|
||||
*/
|
||||
async function simpleSSOLoginDisabledMiddleware(_, response, next) {
|
||||
if (!("multiUserMode" in response.locals)) {
|
||||
const multiUserMode = await SystemSettings.isMultiUserMode();
|
||||
response.locals.multiUserMode = multiUserMode;
|
||||
}
|
||||
|
||||
if (response.locals.multiUserMode && simpleSSOLoginDisabled()) {
|
||||
response.status(403).json({
|
||||
success: false,
|
||||
error: "Login via credentials has been disabled by the administrator.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
simpleSSOEnabled,
|
||||
simpleSSOLoginDisabled,
|
||||
simpleSSOLoginDisabledMiddleware,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user