Skip to content

Commit fd96d10

Browse files
authored
Merge branch 'dev' into list-comp-sortable
2 parents 29201c7 + c75d935 commit fd96d10

File tree

14 files changed

+207
-38
lines changed

14 files changed

+207
-38
lines changed

.github/workflows/sonarcloud.yml

+1
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ jobs:
3030
env:
3131
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
3232
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
33+
SONAR_SCANNER_OPTS: "-Dsonar.javascript.node.maxspace=8192 -Xmx8192m"

client/packages/lowcoder/src/api/apiUtils.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,14 @@ export const apiFailureResponseInterceptor = (error: any) => {
122122
if (!notAuthRequiredPath(error.config?.url)) {
123123
if (error.response.status === API_STATUS_CODES.REQUEST_NOT_AUTHORISED) {
124124
// get x-org-id from failed request
125-
const organizationId = error.response.headers['x-org-id'] || undefined;
125+
let organizationId;
126+
if (error.response.headers['x-org-id']) {
127+
organizationId = error.response.headers['x-org-id'];
128+
}
129+
if (localStorage.getItem('lowcoder_login_orgId')) {
130+
organizationId = localStorage.getItem('lowcoder_login_orgId');
131+
localStorage.removeItem('lowcoder_login_orgId');
132+
}
126133
// Redirect to login and set a redirect url.
127134
StoreRegistry.getStore().dispatch(
128135
logoutAction({

client/packages/lowcoder/src/i18n/locales/en.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3206,6 +3206,7 @@ export const en = {
32063206
"enterPassword": "Enter your password",
32073207
"selectAuthProvider": "Select Authentication Provider",
32083208
"selectWorkspace": "Select your workspace",
3209+
"userNotFound": "User not found. Please make sure you entered the correct email."
32093210
},
32103211
"preLoad": {
32113212
"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.",

client/packages/lowcoder/src/pages/common/profileDropdown.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,13 @@ export default function ProfileDropdown(props: DropDownProps) {
150150
dispatch(profileSettingModalVisible(true));
151151
} else if (e.key === "logout") {
152152
// logout
153-
dispatch(logoutAction({}));
153+
const organizationId = localStorage.getItem('lowcoder_login_orgId');
154+
if (organizationId) {
155+
localStorage.removeItem('lowcoder_login_orgId');
156+
}
157+
dispatch(logoutAction({
158+
organizationId: organizationId || undefined,
159+
}));
154160
} else if (e.keyPath.includes("switchOrg")) {
155161
if (e.key === "newOrganization") {
156162
// create new organization

client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx

+75-21
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
ConfirmButton,
55
StyledRouteLink,
66
} from "pages/userAuth/authComponents";
7-
import React, { useContext, useEffect, useState } from "react";
7+
import React, { useContext, useEffect, useMemo, useState } from "react";
88
import styled from "styled-components";
99
import UserApi from "api/userApi";
1010
import { useRedirectUrl } from "util/hooks";
@@ -19,7 +19,7 @@ import { Divider } from "antd";
1919
import Flex from "antd/es/flex";
2020
import { validateResponse } from "@lowcoder-ee/api/apiUtils";
2121
import OrgApi from "@lowcoder-ee/api/orgApi";
22-
import { AccountLoginWrapper } from "./formLoginAdmin";
22+
import FormLogin, { AccountLoginWrapper } from "./formLoginAdmin";
2323
import { default as Button } from "antd/es/button";
2424
import LeftOutlined from "@ant-design/icons/LeftOutlined";
2525
import { fetchConfigAction } from "@lowcoder-ee/redux/reduxActions/configActions";
@@ -28,6 +28,9 @@ import history from "util/history";
2828
import { getServerSettings } from "@lowcoder-ee/redux/selectors/applicationSelector";
2929
import {fetchOrgPaginationByEmail} from "@lowcoder-ee/util/pagination/axios";
3030
import PaginationComp from "@lowcoder-ee/util/pagination/Pagination";
31+
import { getSystemConfigFetching } from "@lowcoder-ee/redux/selectors/configSelectors";
32+
import Spin from "antd/es/spin";
33+
import LoadingOutlined from "@ant-design/icons/LoadingOutlined";
3134

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

127+
const isEmailLoginEnabled = useMemo(() => {
128+
return isFormLoginEnabled && signinEnabled;
129+
}, [isFormLoginEnabled, signinEnabled]);
130+
131+
const isEnterpriseMode = useMemo(() => {
132+
return serverSettings?.LOWCODER_WORKSPACE_MODE === "ENTERPRISE" || serverSettings?.LOWCODER_WORKSPACE_MODE === "SINGLEWORKSPACE";
133+
}, [serverSettings]);
134+
122135
useEffect(() => {
123136
if (account)
124137
fetchOrgPaginationByEmail({
@@ -133,13 +146,22 @@ export default function FormLoginSteps(props: FormLoginProps) {
133146
}, [pageSize, currentPage])
134147

135148
useEffect(() => {
136-
const { LOWCODER_EMAIL_SIGNUP_ENABLED } = serverSettings;
137-
if (!LOWCODER_EMAIL_SIGNUP_ENABLED) {
138-
return setSignupEnabled(true);
139-
}
149+
const {
150+
LOWCODER_EMAIL_SIGNUP_ENABLED,
151+
LOWCODER_EMAIL_AUTH_ENABLED,
152+
} = serverSettings;
153+
140154
setSignupEnabled(LOWCODER_EMAIL_SIGNUP_ENABLED === 'true');
155+
setSigninEnabled(LOWCODER_EMAIL_AUTH_ENABLED === 'true');
141156
}, [serverSettings]);
142157

158+
const afterLoginSuccess = () => {
159+
if (props.organizationId) {
160+
localStorage.setItem("lowcoder_login_orgId", props.organizationId);
161+
}
162+
fetchUserAfterAuthSuccess?.();
163+
}
164+
143165
const { onSubmit, loading } = useAuthSubmit(
144166
() =>
145167
UserApi.formLogin({
@@ -153,7 +175,7 @@ export default function FormLoginSteps(props: FormLoginProps) {
153175
}),
154176
false,
155177
redirectUrl,
156-
fetchUserAfterAuthSuccess,
178+
afterLoginSuccess,
157179
);
158180

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

169191
setOrgLoading(true);
192+
// for enterprise mode, we will not ask for email in first step
170193
fetchOrgPaginationByEmail({
171-
email: account,
194+
email: isEnterpriseMode ? ' ' : account,
172195
pageNum: currentPage,
173196
pageSize: pageSize
174197
})
@@ -177,15 +200,13 @@ export default function FormLoginSteps(props: FormLoginProps) {
177200
setElements({elements: resp.data || [], total: resp.total || 1})
178201
setOrgList(resp.data);
179202
if (!resp.data.length) {
180-
history.push(
181-
AUTH_REGISTER_URL,
182-
{...location.state || {}, email: account},
183-
)
184-
return;
203+
throw new Error(trans("userAuth.userNotFound"));
185204
}
186205
if (resp.data.length === 1) {
187-
setOrganizationId(resp.data[0].orgId);
188-
dispatch(fetchConfigAction(resp.data[0].orgId));
206+
// in Enterprise mode, we will get org data in different format
207+
const selectedOrgId = isEnterpriseMode ? resp.data[0].id : resp.data[0].orgId;
208+
setOrganizationId(selectedOrgId);
209+
dispatch(fetchConfigAction(selectedOrgId));
189210
setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS);
190211
return;
191212
}
@@ -202,6 +223,39 @@ export default function FormLoginSteps(props: FormLoginProps) {
202223
});
203224
}
204225

226+
useEffect(() => {
227+
if (isEnterpriseMode) {
228+
// dispatch(fetchConfigAction());
229+
fetchOrgsByEmail();
230+
}
231+
}, [isEnterpriseMode]);
232+
233+
if (isEnterpriseMode) {
234+
return (
235+
<Spin indicator={<LoadingOutlined style={{ fontSize: 30 }} />} spinning={isFetchingConfig}>
236+
{ isEmailLoginEnabled && <FormLogin /> }
237+
<ThirdPartyAuth
238+
invitationId={invitationId}
239+
invitedOrganizationId={organizationId}
240+
authGoal="login"
241+
/>
242+
{signupEnabled && (
243+
<>
244+
<Divider/>
245+
<AuthBottomView>
246+
<StyledRouteLink to={{
247+
pathname: AUTH_REGISTER_URL,
248+
state: {...location.state || {}, email: account}
249+
}}>
250+
{trans("userAuth.register")}
251+
</StyledRouteLink>
252+
</AuthBottomView>
253+
</>
254+
)}
255+
</Spin>
256+
);
257+
}
258+
205259
if(currentStep === CurrentStepEnum.EMAIL) {
206260
return (
207261
<>
@@ -227,8 +281,8 @@ export default function FormLoginSteps(props: FormLoginProps) {
227281
<Divider/>
228282
<AuthBottomView>
229283
<StyledRouteLink to={{
230-
pathname: AUTH_REGISTER_URL,
231-
state: location.state
284+
pathname: props.organizationId ? `/org/${props.organizationId}/auth/register` : AUTH_REGISTER_URL,
285+
state: {...location.state || {}, email: account}
232286
}}>
233287
{trans("userAuth.register")}
234288
</StyledRouteLink>
@@ -280,10 +334,10 @@ export default function FormLoginSteps(props: FormLoginProps) {
280334
}} />
281335
<StepHeader
282336
title={
283-
isFormLoginEnabled ? trans("userAuth.enterPassword") : trans("userAuth.selectAuthProvider")
337+
isEmailLoginEnabled ? trans("userAuth.enterPassword") : trans("userAuth.selectAuthProvider")
284338
}
285339
/>
286-
{isFormLoginEnabled && (
340+
{isEmailLoginEnabled && (
287341
<>
288342
<PasswordInput
289343
className="form-input password-input"
@@ -315,7 +369,7 @@ export default function FormLoginSteps(props: FormLoginProps) {
315369
/>
316370
)}
317371
</AccountLoginWrapper>
318-
{isFormLoginEnabled && signupEnabled && (
372+
{isEmailLoginEnabled && signupEnabled && (
319373
<>
320374
<Divider/>
321375
<AuthBottomView>

client/packages/lowcoder/src/pages/userAuth/register.tsx

+14-3
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,16 @@ function UserRegister() {
6363
return inviteInfo?.invitedOrganizationId;
6464
}
6565
return orgId;
66-
}, [ inviteInfo, orgId ])
66+
}, [ inviteInfo, orgId ]);
6767

6868
const authId = systemConfig?.form.id;
6969

7070
const serverSettings = useSelector(getServerSettings);
7171

72+
const isEnterpriseMode = useMemo(() => {
73+
return serverSettings?.LOWCODER_WORKSPACE_MODE === "ENTERPRISE" || serverSettings?.LOWCODER_WORKSPACE_MODE === "SINGLEWORKSPACE";
74+
}, [serverSettings]);
75+
7276
useEffect(() => {
7377
const { LOWCODER_EMAIL_SIGNUP_ENABLED } = serverSettings;
7478
if(
@@ -82,6 +86,13 @@ function UserRegister() {
8286
};
8387
}, [serverSettings]);
8488

89+
const afterLoginSuccess = () => {
90+
if (organizationId) {
91+
localStorage.setItem("lowcoder_login_orgId", organizationId);
92+
}
93+
fetchUserAfterAuthSuccess?.();
94+
}
95+
8596
const { loading, onSubmit } = useAuthSubmit(
8697
() =>
8798
UserApi.formLogin({
@@ -95,7 +106,7 @@ function UserRegister() {
95106
}),
96107
false,
97108
redirectUrl,
98-
fetchUserAfterAuthSuccess,
109+
afterLoginSuccess,
99110
);
100111

101112
const checkEmailExist = () => {
@@ -160,7 +171,7 @@ function UserRegister() {
160171
{trans("userAuth.register")}
161172
</ConfirmButton>
162173
<TermsAndPrivacyInfo onCheckChange={(e) => setSubmitBtnDisable(!e.target.checked)} />
163-
{organizationId && (
174+
{(organizationId || isEnterpriseMode) && (
164175
<ThirdPartyAuth
165176
invitationId={invitationId}
166177
invitedOrganizationId={organizationId}

client/packages/lowcoder/src/pages/userAuth/thirdParty/thirdPartyAuth.tsx

+15-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import history from "util/history";
88
import { LoginLogoStyle, LoginLabelStyle, StyledLoginButton } from "pages/userAuth/authComponents";
99
import { useSelector } from "react-redux";
1010
import { getSystemConfigFetching, selectSystemConfig } from "redux/selectors/configSelectors";
11-
import React from "react";
11+
import React, { useMemo } from "react";
1212
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
1313
import styled from "styled-components";
1414
import { trans } from "i18n";
@@ -19,6 +19,7 @@ import { useRedirectUrl } from "util/hooks";
1919
import { MultiIconDisplay } from "../../../comps/comps/multiIconDisplay";
2020
import Spin from "antd/es/spin";
2121
import { LoadingOutlined } from "@ant-design/icons";
22+
import { getServerSettings } from "@lowcoder-ee/redux/selectors/applicationSelector";
2223

2324
const { Text } = Typography;
2425

@@ -111,7 +112,16 @@ export function ThirdPartyAuth(props: {
111112
}) {
112113
const systemConfigFetching = useSelector(getSystemConfigFetching);
113114
const systemConfig = useSelector(selectSystemConfig);
115+
const serverSettings = useSelector(getServerSettings);
114116
const isFormLoginEnabled = systemConfig?.form.enableLogin;
117+
118+
const isEmailLoginEnabled = useMemo(() => {
119+
return isFormLoginEnabled && serverSettings.LOWCODER_EMAIL_AUTH_ENABLED === 'true';
120+
}, [isFormLoginEnabled, serverSettings]);
121+
122+
const isEmailSignupEnabled = useMemo(() => {
123+
return serverSettings.LOWCODER_EMAIL_SIGNUP_ENABLED === 'true';
124+
}, [serverSettings]);
115125

116126
if (systemConfigFetching) {
117127
return <Spin indicator={<LoadingOutlined style={{ fontSize: 15, marginTop: '16px' }} spin />} />;
@@ -140,7 +150,10 @@ export function ThirdPartyAuth(props: {
140150
});
141151
return (
142152
<ThirdPartyLoginButtonWrapper>
143-
{ isFormLoginEnabled && Boolean(socialLoginButtons.length) && (
153+
{ (
154+
(isEmailLoginEnabled && props.authGoal === 'login')
155+
|| (isEmailSignupEnabled && props.authGoal === 'register')
156+
) && Boolean(socialLoginButtons.length) && (
144157
<Divider plain>
145158
<Text type="secondary">or</Text>
146159
</Divider>

server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/service/EmailCommunicationService.java

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
public interface EmailCommunicationService {
44
boolean sendPasswordResetEmail(String to, String token, String message);
5+
boolean sendInvitationEmails(String[] to, String inviteLink, String message);
56
}

server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/user/service/EmailCommunicationServiceImpl.java

+28
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,32 @@ public boolean sendPasswordResetEmail(String to, String token, String message) {
4949

5050
}
5151

52+
@Override
53+
public boolean sendInvitationEmails(String[] to, String inviteLink, String message) {
54+
try {
55+
String subject = "You've been invited!";
56+
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
57+
58+
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
59+
60+
mimeMessageHelper.setFrom(config.getNotificationsEmailSender());
61+
mimeMessageHelper.setTo(to);
62+
mimeMessageHelper.setSubject(subject);
63+
64+
// Construct the message with the invite link
65+
String formattedMessage = String.format(message, inviteLink);
66+
mimeMessageHelper.setText(formattedMessage, true); // Set HTML to true to allow links
67+
68+
javaMailSender.send(mimeMessage);
69+
70+
return true;
71+
72+
} catch (Exception e) {
73+
log.error("Failed to send mail to: {}, Exception: ", to, e);
74+
return false;
75+
}
76+
77+
78+
}
79+
5280
}

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/authentication/AuthenticationController.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.lowcoder.api.authentication;
22

3-
import lombok.RequiredArgsConstructor;
3+
import java.util.List;
4+
45
import org.lowcoder.api.authentication.dto.APIKeyRequest;
56
import org.lowcoder.api.authentication.dto.AuthConfigRequest;
67
import org.lowcoder.api.authentication.service.AuthenticationApiService;
@@ -20,9 +21,9 @@
2021
import org.springframework.web.bind.annotation.RequestParam;
2122
import org.springframework.web.bind.annotation.RestController;
2223
import org.springframework.web.server.ServerWebExchange;
23-
import reactor.core.publisher.Mono;
2424

25-
import java.util.List;
25+
import lombok.RequiredArgsConstructor;
26+
import reactor.core.publisher.Mono;
2627

2728
@RequiredArgsConstructor
2829
@RestController

0 commit comments

Comments
 (0)