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:
parent
feadf1f0ff
commit
2055c8accd
2
.github/workflows/dev-build.yaml
vendored
2
.github/workflows/dev-build.yaml
vendored
@ -6,7 +6,7 @@ concurrency:
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
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:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- 'cloud-deployments/*'
|
- 'cloud-deployments/*'
|
||||||
|
|||||||
@ -329,6 +329,7 @@ GID='1000'
|
|||||||
# Enable simple SSO passthrough to pre-authenticate users from a third party service.
|
# 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.
|
# See https://docs.anythingllm.com/configuration#simple-sso-passthrough for more information.
|
||||||
# SIMPLE_SSO_ENABLED=1
|
# SIMPLE_SSO_ENABLED=1
|
||||||
|
# SIMPLE_SSO_NO_LOGIN=1
|
||||||
|
|
||||||
# Allow scraping of any IP address in collector - must be string "true" to be enabled
|
# 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.
|
# 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)
|
if (error)
|
||||||
return (
|
return (
|
||||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex items-center justify-center flex-col gap-4">
|
<div className="w-screen h-screen overflow-hidden bg-theme-bg-primary flex items-center justify-center flex-col gap-4">
|
||||||
<p className="text-white font-mono text-lg">{error}</p>
|
<p className="text-theme-text-primary font-mono text-lg">{error}</p>
|
||||||
<p className="text-white/80 font-mono text-sm">
|
<p className="text-theme-text-secondary font-mono text-sm">
|
||||||
Please contact the system administrator about this error.
|
Please contact the system administrator about this error.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,11 +4,24 @@ import { FullScreenLoader } from "@/components/Preloader";
|
|||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import paths from "@/utils/paths";
|
import paths from "@/utils/paths";
|
||||||
import useQuery from "@/hooks/useQuery";
|
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() {
|
export default function Login() {
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
|
const { loading: ssoLoading, ssoConfig } = useSimpleSSO();
|
||||||
const { loading, requiresAuth, mode } = usePasswordModal(!!query.get("nt"));
|
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()} />;
|
if (requiresAuth === false) return <Navigate to={paths.home()} />;
|
||||||
|
|
||||||
return <PasswordModal mode={mode} />;
|
return <PasswordModal mode={mode} />;
|
||||||
|
|||||||
@ -284,7 +284,7 @@ const MyTeam = ({ setMultiUserLoginValid, myTeamSubmitRef, navigate }) => {
|
|||||||
htmlFor="name"
|
htmlFor="name"
|
||||||
className="block mb-3 text-sm font-medium text-white"
|
className="block mb-3 text-sm font-medium text-white"
|
||||||
>
|
>
|
||||||
{t("common.adminUsername")}
|
{t("onboarding.userSetup.adminUsername")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
name="username"
|
name="username"
|
||||||
|
|||||||
@ -18,6 +18,11 @@ export default {
|
|||||||
login: (noTry = false) => {
|
login: (noTry = false) => {
|
||||||
return `/login${noTry ? "?nt=1" : ""}`;
|
return `/login${noTry ? "?nt=1" : ""}`;
|
||||||
},
|
},
|
||||||
|
sso: {
|
||||||
|
login: () => {
|
||||||
|
return "/sso/simple";
|
||||||
|
},
|
||||||
|
},
|
||||||
onboarding: {
|
onboarding: {
|
||||||
home: () => {
|
home: () => {
|
||||||
return "/onboarding";
|
return "/onboarding";
|
||||||
|
|||||||
@ -326,6 +326,7 @@ TTS_PROVIDER="native"
|
|||||||
# Enable simple SSO passthrough to pre-authenticate users from a third party service.
|
# 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.
|
# See https://docs.anythingllm.com/configuration#simple-sso-passthrough for more information.
|
||||||
# SIMPLE_SSO_ENABLED=1
|
# SIMPLE_SSO_ENABLED=1
|
||||||
|
# SIMPLE_SSO_NO_LOGIN=1
|
||||||
|
|
||||||
# Allow scraping of any IP address in collector - must be string "true" to be enabled
|
# 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.
|
# See https://docs.anythingllm.com/configuration#local-ip-address-scraping for more information.
|
||||||
|
|||||||
@ -25,6 +25,9 @@ const {
|
|||||||
} = require("../utils/middleware/multiUserProtected");
|
} = require("../utils/middleware/multiUserProtected");
|
||||||
const { validatedRequest } = require("../utils/middleware/validatedRequest");
|
const { validatedRequest } = require("../utils/middleware/validatedRequest");
|
||||||
const ImportedPlugin = require("../utils/agents/imported");
|
const ImportedPlugin = require("../utils/agents/imported");
|
||||||
|
const {
|
||||||
|
simpleSSOLoginDisabledMiddleware,
|
||||||
|
} = require("../utils/middleware/simpleSSOEnabled");
|
||||||
|
|
||||||
function adminEndpoints(app) {
|
function adminEndpoints(app) {
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
@ -168,7 +171,11 @@ function adminEndpoints(app) {
|
|||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
"/admin/invite/new",
|
"/admin/invite/new",
|
||||||
[validatedRequest, strictMultiUserRoleValid([ROLES.admin, ROLES.manager])],
|
[
|
||||||
|
validatedRequest,
|
||||||
|
strictMultiUserRoleValid([ROLES.admin, ROLES.manager]),
|
||||||
|
simpleSSOLoginDisabledMiddleware,
|
||||||
|
],
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
const user = await userFromSession(request, response);
|
||||||
|
|||||||
@ -2,6 +2,9 @@ const { EventLogs } = require("../models/eventLogs");
|
|||||||
const { Invite } = require("../models/invite");
|
const { Invite } = require("../models/invite");
|
||||||
const { User } = require("../models/user");
|
const { User } = require("../models/user");
|
||||||
const { reqBody } = require("../utils/http");
|
const { reqBody } = require("../utils/http");
|
||||||
|
const {
|
||||||
|
simpleSSOLoginDisabledMiddleware,
|
||||||
|
} = require("../utils/middleware/simpleSSOEnabled");
|
||||||
|
|
||||||
function inviteEndpoints(app) {
|
function inviteEndpoints(app) {
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
@ -31,46 +34,50 @@ function inviteEndpoints(app) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/invite/:code", async (request, response) => {
|
app.post(
|
||||||
try {
|
"/invite/:code",
|
||||||
const { code } = request.params;
|
[simpleSSOLoginDisabledMiddleware],
|
||||||
const { username, password } = reqBody(request);
|
async (request, response) => {
|
||||||
const invite = await Invite.get({ code });
|
try {
|
||||||
if (!invite || invite.status !== "pending") {
|
const { code } = request.params;
|
||||||
response
|
const { username, password } = reqBody(request);
|
||||||
.status(200)
|
const invite = await Invite.get({ code });
|
||||||
.json({ success: false, error: "Invite not found or is invalid." });
|
if (!invite || invite.status !== "pending") {
|
||||||
return;
|
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 };
|
module.exports = { inviteEndpoints };
|
||||||
|
|||||||
@ -54,7 +54,10 @@ const { BrowserExtensionApiKey } = require("../models/browserExtensionApiKey");
|
|||||||
const {
|
const {
|
||||||
chatHistoryViewable,
|
chatHistoryViewable,
|
||||||
} = require("../utils/middleware/chatHistoryViewable");
|
} = require("../utils/middleware/chatHistoryViewable");
|
||||||
const { simpleSSOEnabled } = require("../utils/middleware/simpleSSOEnabled");
|
const {
|
||||||
|
simpleSSOEnabled,
|
||||||
|
simpleSSOLoginDisabled,
|
||||||
|
} = require("../utils/middleware/simpleSSOEnabled");
|
||||||
const { TemporaryAuthToken } = require("../models/temporaryAuthToken");
|
const { TemporaryAuthToken } = require("../models/temporaryAuthToken");
|
||||||
const { SystemPromptVariables } = require("../models/systemPromptVariables");
|
const { SystemPromptVariables } = require("../models/systemPromptVariables");
|
||||||
const { VALID_COMMANDS } = require("../utils/chats");
|
const { VALID_COMMANDS } = require("../utils/chats");
|
||||||
@ -116,6 +119,17 @@ function systemEndpoints(app) {
|
|||||||
const bcrypt = require("bcrypt");
|
const bcrypt = require("bcrypt");
|
||||||
|
|
||||||
if (await SystemSettings.isMultiUserMode()) {
|
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 { username, password } = reqBody(request);
|
||||||
const existingUser = await User._get({ username: String(username) });
|
const existingUser = await User._get({ username: String(username) });
|
||||||
|
|
||||||
|
|||||||
@ -279,6 +279,12 @@ const SystemSettings = {
|
|||||||
// Disable View Chat History for the whole instance.
|
// Disable View Chat History for the whole instance.
|
||||||
DisableViewChatHistory:
|
DisableViewChatHistory:
|
||||||
"DISABLE_VIEW_CHAT_HISTORY" in process.env || false,
|
"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",
|
"DISABLE_VIEW_CHAT_HISTORY",
|
||||||
// Simple SSO
|
// Simple SSO
|
||||||
"SIMPLE_SSO_ENABLED",
|
"SIMPLE_SSO_ENABLED",
|
||||||
|
"SIMPLE_SSO_NO_LOGIN",
|
||||||
// Community Hub
|
// Community Hub
|
||||||
"COMMUNITY_HUB_BUNDLE_DOWNLOADS_ENABLED",
|
"COMMUNITY_HUB_BUNDLE_DOWNLOADS_ENABLED",
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,51 @@ async function simpleSSOEnabled(_, response, next) {
|
|||||||
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 = {
|
module.exports = {
|
||||||
simpleSSOEnabled,
|
simpleSSOEnabled,
|
||||||
|
simpleSSOLoginDisabled,
|
||||||
|
simpleSSOLoginDisabledMiddleware,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user