Skip to content

Login flow for SingleWorkspace/Enterprise mode #1643

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion client/packages/lowcoder/src/api/apiUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,14 @@ export const apiFailureResponseInterceptor = (error: any) => {
if (!notAuthRequiredPath(error.config?.url)) {
if (error.response.status === API_STATUS_CODES.REQUEST_NOT_AUTHORISED) {
// get x-org-id from failed request
const organizationId = error.response.headers['x-org-id'] || undefined;
let organizationId;
if (error.response.headers['x-org-id']) {
organizationId = error.response.headers['x-org-id'];
}
if (localStorage.getItem('lowcoder_login_orgId')) {
organizationId = localStorage.getItem('lowcoder_login_orgId');
localStorage.removeItem('lowcoder_login_orgId');
}
// Redirect to login and set a redirect url.
StoreRegistry.getStore().dispatch(
logoutAction({
Expand Down
1 change: 1 addition & 0 deletions client/packages/lowcoder/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3205,6 +3205,7 @@ export const en = {
"enterPassword": "Enter your password",
"selectAuthProvider": "Select Authentication Provider",
"selectWorkspace": "Select your workspace",
"userNotFound": "User not found. Please make sure you entered the correct email."
},
"preLoad": {
"jsLibraryHelpText": "Add JavaScript Libraries to Your Current Application via URL Addresses. lodash, day.js, uuid, numbro are Built into the System for Immediate Use. JavaScript Libraries are Loaded Before the Application is Initialized, Which Can Have an Impact on Application Performance.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,13 @@ export default function ProfileDropdown(props: DropDownProps) {
dispatch(profileSettingModalVisible(true));
} else if (e.key === "logout") {
// logout
dispatch(logoutAction({}));
const organizationId = localStorage.getItem('lowcoder_login_orgId');
if (organizationId) {
localStorage.removeItem('lowcoder_login_orgId');
}
dispatch(logoutAction({
organizationId: organizationId || undefined,
}));
} else if (e.keyPath.includes("switchOrg")) {
if (e.key === "newOrganization") {
// create new organization
Expand Down
96 changes: 75 additions & 21 deletions client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
ConfirmButton,
StyledRouteLink,
} from "pages/userAuth/authComponents";
import React, { useContext, useEffect, useState } from "react";
import React, { useContext, useEffect, useMemo, useState } from "react";
import styled from "styled-components";
import UserApi from "api/userApi";
import { useRedirectUrl } from "util/hooks";
Expand All @@ -19,7 +19,7 @@ import { Divider } from "antd";
import Flex from "antd/es/flex";
import { validateResponse } from "@lowcoder-ee/api/apiUtils";
import OrgApi from "@lowcoder-ee/api/orgApi";
import { AccountLoginWrapper } from "./formLoginAdmin";
import FormLogin, { AccountLoginWrapper } from "./formLoginAdmin";
import { default as Button } from "antd/es/button";
import LeftOutlined from "@ant-design/icons/LeftOutlined";
import { fetchConfigAction } from "@lowcoder-ee/redux/reduxActions/configActions";
Expand All @@ -28,6 +28,9 @@ import history from "util/history";
import { getServerSettings } from "@lowcoder-ee/redux/selectors/applicationSelector";
import {fetchOrgPaginationByEmail} from "@lowcoder-ee/util/pagination/axios";
import PaginationComp from "@lowcoder-ee/util/pagination/Pagination";
import { getSystemConfigFetching } from "@lowcoder-ee/redux/selectors/configSelectors";
import Spin from "antd/es/spin";
import LoadingOutlined from "@ant-design/icons/LoadingOutlined";

const StyledCard = styled.div<{$selected: boolean}>`
display: flex;
Expand Down Expand Up @@ -107,18 +110,28 @@ export default function FormLoginSteps(props: FormLoginProps) {
const { systemConfig, inviteInfo, fetchUserAfterAuthSuccess } = useContext(AuthContext);
const invitationId = inviteInfo?.invitationId;
const authId = systemConfig?.form.id;
const isFormLoginEnabled = systemConfig?.form.enableLogin;
const isFormLoginEnabled = systemConfig?.form.enableLogin; // check from configs
const [orgLoading, setOrgLoading] = useState(false);
const [orgList, setOrgList] = useState<OrgItem[]>([]);
const [currentStep, setCurrentStep] = useState<CurrentStepEnum>(CurrentStepEnum.EMAIL);
const [organizationId, setOrganizationId] = useState<string|undefined>(props.organizationId);
const [skipWorkspaceStep, setSkipWorkspaceStep] = useState<boolean>(false);
const [signupEnabled, setSignupEnabled] = useState<boolean>(true);
const [signinEnabled, setSigninEnabled] = useState<boolean>(true); // check from server settings
const serverSettings = useSelector(getServerSettings);
const isFetchingConfig = useSelector(getSystemConfigFetching);
const [elements, setElements] = useState<ElementsState>({ elements: [], total: 0 });
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);

const isEmailLoginEnabled = useMemo(() => {
return isFormLoginEnabled && signinEnabled;
}, [isFormLoginEnabled, signinEnabled]);

const isEnterpriseMode = useMemo(() => {
return serverSettings?.LOWCODER_WORKSPACE_MODE === "ENTERPRISE" || serverSettings?.LOWCODER_WORKSPACE_MODE === "SINGLEWORKSPACE";
}, [serverSettings]);

useEffect(() => {
if (account)
fetchOrgPaginationByEmail({
Expand All @@ -133,13 +146,22 @@ export default function FormLoginSteps(props: FormLoginProps) {
}, [pageSize, currentPage])

useEffect(() => {
const { LOWCODER_EMAIL_SIGNUP_ENABLED } = serverSettings;
if (!LOWCODER_EMAIL_SIGNUP_ENABLED) {
return setSignupEnabled(true);
}
const {
LOWCODER_EMAIL_SIGNUP_ENABLED,
LOWCODER_EMAIL_AUTH_ENABLED,
} = serverSettings;

setSignupEnabled(LOWCODER_EMAIL_SIGNUP_ENABLED === 'true');
setSigninEnabled(LOWCODER_EMAIL_AUTH_ENABLED === 'true');
}, [serverSettings]);

const afterLoginSuccess = () => {
if (props.organizationId) {
localStorage.setItem("lowcoder_login_orgId", props.organizationId);
}
fetchUserAfterAuthSuccess?.();
}

const { onSubmit, loading } = useAuthSubmit(
() =>
UserApi.formLogin({
Expand All @@ -153,7 +175,7 @@ export default function FormLoginSteps(props: FormLoginProps) {
}),
false,
redirectUrl,
fetchUserAfterAuthSuccess,
afterLoginSuccess,
);

const fetchOrgsByEmail = () => {
Expand All @@ -167,8 +189,9 @@ export default function FormLoginSteps(props: FormLoginProps) {
}

setOrgLoading(true);
// for enterprise mode, we will not ask for email in first step
fetchOrgPaginationByEmail({
email: account,
email: isEnterpriseMode ? ' ' : account,
pageNum: currentPage,
pageSize: pageSize
})
Expand All @@ -177,15 +200,13 @@ export default function FormLoginSteps(props: FormLoginProps) {
setElements({elements: resp.data || [], total: resp.total || 1})
setOrgList(resp.data);
if (!resp.data.length) {
history.push(
AUTH_REGISTER_URL,
{...location.state || {}, email: account},
)
return;
throw new Error(trans("userAuth.userNotFound"));
}
if (resp.data.length === 1) {
setOrganizationId(resp.data[0].orgId);
dispatch(fetchConfigAction(resp.data[0].orgId));
// in Enterprise mode, we will get org data in different format
const selectedOrgId = isEnterpriseMode ? resp.data[0].id : resp.data[0].orgId;
setOrganizationId(selectedOrgId);
dispatch(fetchConfigAction(selectedOrgId));
setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS);
return;
}
Expand All @@ -202,6 +223,39 @@ export default function FormLoginSteps(props: FormLoginProps) {
});
}

useEffect(() => {
if (isEnterpriseMode) {
// dispatch(fetchConfigAction());
fetchOrgsByEmail();
}
}, [isEnterpriseMode]);

if (isEnterpriseMode) {
return (
<Spin indicator={<LoadingOutlined style={{ fontSize: 30 }} />} spinning={isFetchingConfig}>
{ isEmailLoginEnabled && <FormLogin /> }
<ThirdPartyAuth
invitationId={invitationId}
invitedOrganizationId={organizationId}
authGoal="login"
/>
{signupEnabled && (
<>
<Divider/>
<AuthBottomView>
<StyledRouteLink to={{
pathname: AUTH_REGISTER_URL,
state: {...location.state || {}, email: account}
}}>
{trans("userAuth.register")}
</StyledRouteLink>
</AuthBottomView>
</>
)}
</Spin>
);
}

if(currentStep === CurrentStepEnum.EMAIL) {
return (
<>
Expand All @@ -227,8 +281,8 @@ export default function FormLoginSteps(props: FormLoginProps) {
<Divider/>
<AuthBottomView>
<StyledRouteLink to={{
pathname: AUTH_REGISTER_URL,
state: location.state
pathname: props.organizationId ? `/org/${props.organizationId}/auth/register` : AUTH_REGISTER_URL,
state: {...location.state || {}, email: account}
}}>
{trans("userAuth.register")}
</StyledRouteLink>
Expand Down Expand Up @@ -280,10 +334,10 @@ export default function FormLoginSteps(props: FormLoginProps) {
}} />
<StepHeader
title={
isFormLoginEnabled ? trans("userAuth.enterPassword") : trans("userAuth.selectAuthProvider")
isEmailLoginEnabled ? trans("userAuth.enterPassword") : trans("userAuth.selectAuthProvider")
}
/>
{isFormLoginEnabled && (
{isEmailLoginEnabled && (
<>
<PasswordInput
className="form-input password-input"
Expand Down Expand Up @@ -315,7 +369,7 @@ export default function FormLoginSteps(props: FormLoginProps) {
/>
)}
</AccountLoginWrapper>
{isFormLoginEnabled && signupEnabled && (
{isEmailLoginEnabled && signupEnabled && (
<>
<Divider/>
<AuthBottomView>
Expand Down
17 changes: 14 additions & 3 deletions client/packages/lowcoder/src/pages/userAuth/register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,16 @@ function UserRegister() {
return inviteInfo?.invitedOrganizationId;
}
return orgId;
}, [ inviteInfo, orgId ])
}, [ inviteInfo, orgId ]);

const authId = systemConfig?.form.id;

const serverSettings = useSelector(getServerSettings);

const isEnterpriseMode = useMemo(() => {
return serverSettings?.LOWCODER_WORKSPACE_MODE === "ENTERPRISE" || serverSettings?.LOWCODER_WORKSPACE_MODE === "SINGLEWORKSPACE";
}, [serverSettings]);

useEffect(() => {
const { LOWCODER_EMAIL_SIGNUP_ENABLED } = serverSettings;
if(
Expand All @@ -82,6 +86,13 @@ function UserRegister() {
};
}, [serverSettings]);

const afterLoginSuccess = () => {
if (organizationId) {
localStorage.setItem("lowcoder_login_orgId", organizationId);
}
fetchUserAfterAuthSuccess?.();
}

const { loading, onSubmit } = useAuthSubmit(
() =>
UserApi.formLogin({
Expand All @@ -95,7 +106,7 @@ function UserRegister() {
}),
false,
redirectUrl,
fetchUserAfterAuthSuccess,
afterLoginSuccess,
);

const checkEmailExist = () => {
Expand Down Expand Up @@ -160,7 +171,7 @@ function UserRegister() {
{trans("userAuth.register")}
</ConfirmButton>
<TermsAndPrivacyInfo onCheckChange={(e) => setSubmitBtnDisable(!e.target.checked)} />
{organizationId && (
{(organizationId || isEnterpriseMode) && (
<ThirdPartyAuth
invitationId={invitationId}
invitedOrganizationId={organizationId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import history from "util/history";
import { LoginLogoStyle, LoginLabelStyle, StyledLoginButton } from "pages/userAuth/authComponents";
import { useSelector } from "react-redux";
import { getSystemConfigFetching, selectSystemConfig } from "redux/selectors/configSelectors";
import React from "react";
import React, { useMemo } from "react";
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
import styled from "styled-components";
import { trans } from "i18n";
Expand All @@ -19,6 +19,7 @@ import { useRedirectUrl } from "util/hooks";
import { MultiIconDisplay } from "../../../comps/comps/multiIconDisplay";
import Spin from "antd/es/spin";
import { LoadingOutlined } from "@ant-design/icons";
import { getServerSettings } from "@lowcoder-ee/redux/selectors/applicationSelector";

const { Text } = Typography;

Expand Down Expand Up @@ -111,7 +112,16 @@ export function ThirdPartyAuth(props: {
}) {
const systemConfigFetching = useSelector(getSystemConfigFetching);
const systemConfig = useSelector(selectSystemConfig);
const serverSettings = useSelector(getServerSettings);
const isFormLoginEnabled = systemConfig?.form.enableLogin;

const isEmailLoginEnabled = useMemo(() => {
return isFormLoginEnabled && serverSettings.LOWCODER_EMAIL_AUTH_ENABLED === 'true';
}, [isFormLoginEnabled, serverSettings]);

const isEmailSignupEnabled = useMemo(() => {
return serverSettings.LOWCODER_EMAIL_SIGNUP_ENABLED === 'true';
}, [serverSettings]);

if (systemConfigFetching) {
return <Spin indicator={<LoadingOutlined style={{ fontSize: 15, marginTop: '16px' }} spin />} />;
Expand Down Expand Up @@ -140,7 +150,10 @@ export function ThirdPartyAuth(props: {
});
return (
<ThirdPartyLoginButtonWrapper>
{ isFormLoginEnabled && Boolean(socialLoginButtons.length) && (
{ (
(isEmailLoginEnabled && props.authGoal === 'login')
|| (isEmailSignupEnabled && props.authGoal === 'register')
) && Boolean(socialLoginButtons.length) && (
<Divider plain>
<Text type="secondary">or</Text>
</Divider>
Expand Down
Loading