Skip to content

Commit 1825e59

Browse files
authored
Merge pull request #687 from actiontech/feature/sqle-ee-issue-2448
[feature]: support redirecting to the target page after successful oauth2 login on the login page
2 parents 578c3d6 + 63fae68 commit 1825e59

6 files changed

Lines changed: 173 additions & 13 deletions

File tree

packages/base/src/page/BindUser/__snapshots__/index.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ exports[`page/BindUser-ee render bind user form item render form item error when
230230
</body>
231231
`;
232232

233-
exports[`page/BindUser-ee render bind user form item render oauth2_token params submit 1`] = `
233+
exports[`page/BindUser-ee render bind user form item render oauth2_token params submit without target param 1`] = `
234234
<body>
235235
<div>
236236
<section

packages/base/src/page/BindUser/index.test.tsx

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ describe('page/BindUser-ee', () => {
108108
);
109109
});
110110

111-
it('render oauth2_token params submit', async () => {
111+
it('render oauth2_token params submit without target param', async () => {
112112
const LocalStorageWrapperSet = jest.spyOn(LocalStorageWrapper, 'set');
113113
const search = `oauth2_token=oauth2_token_val&refresh_token=id_token_val`;
114114
const requestFn = dms.bindUser();
@@ -157,6 +157,113 @@ describe('page/BindUser-ee', () => {
157157
CompanyNoticeDisplayStatusEnum.NotDisplayed
158158
);
159159
});
160+
161+
it('render oauth2_token params submit with target param', async () => {
162+
const LocalStorageWrapperSet = jest.spyOn(LocalStorageWrapper, 'set');
163+
const search = `oauth2_token=oauth2_token_val&refresh_token=id_token_val&target=${encodeURIComponent(
164+
'/project/test'
165+
)}`;
166+
const requestFn = dms.bindUser();
167+
const { baseElement } = customRender(`/user/bind?${search}`);
168+
await act(async () => jest.advanceTimersByTime(300));
169+
170+
fireEvent.change(getBySelector('#username', baseElement), {
171+
target: {
172+
value: 'oauth2_admin'
173+
}
174+
});
175+
await act(async () => jest.advanceTimersByTime(300));
176+
fireEvent.change(getBySelector('#password', baseElement), {
177+
target: {
178+
value: 'oauth2_admin'
179+
}
180+
});
181+
await act(async () => jest.advanceTimersByTime(300));
182+
183+
await act(async () => {
184+
fireEvent.click(getBySelector('.login-btn', baseElement));
185+
await act(async () => jest.advanceTimersByTime(300));
186+
});
187+
await act(async () => jest.advanceTimersByTime(3000));
188+
expect(requestFn).toHaveBeenCalled();
189+
await act(async () => jest.advanceTimersByTime(300));
190+
expect(dispatchSpy).toHaveBeenCalledTimes(1);
191+
expect(navigateSpy).toHaveBeenCalled();
192+
expect(navigateSpy).toHaveBeenCalledWith('/project/test');
193+
expect(LocalStorageWrapperSet).toHaveBeenCalled();
194+
});
195+
196+
it('render oauth2_token params submit with target param containing query params', async () => {
197+
const LocalStorageWrapperSet = jest.spyOn(LocalStorageWrapper, 'set');
198+
const search = `oauth2_token=oauth2_token_val&refresh_token=id_token_val&target=${encodeURIComponent(
199+
'/project/test?active=overview'
200+
)}`;
201+
const requestFn = dms.bindUser();
202+
const { baseElement } = customRender(`/user/bind?${search}`);
203+
await act(async () => jest.advanceTimersByTime(300));
204+
205+
fireEvent.change(getBySelector('#username', baseElement), {
206+
target: {
207+
value: 'oauth2_admin'
208+
}
209+
});
210+
await act(async () => jest.advanceTimersByTime(300));
211+
fireEvent.change(getBySelector('#password', baseElement), {
212+
target: {
213+
value: 'oauth2_admin'
214+
}
215+
});
216+
await act(async () => jest.advanceTimersByTime(300));
217+
218+
await act(async () => {
219+
fireEvent.click(getBySelector('.login-btn', baseElement));
220+
await act(async () => jest.advanceTimersByTime(300));
221+
});
222+
await act(async () => jest.advanceTimersByTime(3000));
223+
expect(requestFn).toHaveBeenCalled();
224+
await act(async () => jest.advanceTimersByTime(300));
225+
expect(dispatchSpy).toHaveBeenCalledTimes(1);
226+
expect(navigateSpy).toHaveBeenCalled();
227+
expect(navigateSpy).toHaveBeenCalledWith('/project/test?active=overview');
228+
expect(LocalStorageWrapperSet).toHaveBeenCalled();
229+
});
230+
231+
it('render oauth2_token params submit with cloud-beaver target param', async () => {
232+
const LocalStorageWrapperSet = jest.spyOn(LocalStorageWrapper, 'set');
233+
const search = `oauth2_token=oauth2_token_val&refresh_token=id_token_val&target=${encodeURIComponent(
234+
'/cloud-beaver'
235+
)}`;
236+
const requestFn = dms.bindUser();
237+
const { baseElement } = customRender(`/user/bind?${search}`);
238+
await act(async () => jest.advanceTimersByTime(300));
239+
240+
fireEvent.change(getBySelector('#username', baseElement), {
241+
target: {
242+
value: 'oauth2_admin'
243+
}
244+
});
245+
await act(async () => jest.advanceTimersByTime(300));
246+
fireEvent.change(getBySelector('#password', baseElement), {
247+
target: {
248+
value: 'oauth2_admin'
249+
}
250+
});
251+
await act(async () => jest.advanceTimersByTime(300));
252+
253+
await act(async () => {
254+
fireEvent.click(getBySelector('.login-btn', baseElement));
255+
await act(async () => jest.advanceTimersByTime(300));
256+
});
257+
await act(async () => jest.advanceTimersByTime(3000));
258+
expect(requestFn).toHaveBeenCalled();
259+
await act(async () => jest.advanceTimersByTime(300));
260+
expect(dispatchSpy).toHaveBeenCalledTimes(1);
261+
expect(navigateSpy).toHaveBeenCalled();
262+
expect(navigateSpy).toHaveBeenCalledWith(
263+
'/cloud-beaver?open_cloud_beaver=true'
264+
);
265+
expect(LocalStorageWrapperSet).toHaveBeenCalled();
266+
});
160267
});
161268

162269
describe('render url have search val', () => {
@@ -226,6 +333,16 @@ describe('page/BindUser-ee', () => {
226333
});
227334
});
228335

336+
it('render oauth2_token params submit with target param', async () => {
337+
const search = `dms_token=oauth2_token_val&user_exist=true&refresh_token=id_token_val&target=${encodeURIComponent(
338+
'/project/test'
339+
)}`;
340+
customRender(`/user/bind?${search}`);
341+
await act(async () => jest.advanceTimersByTime(300));
342+
expect(navigateSpy).toHaveBeenCalled();
343+
expect(navigateSpy).toHaveBeenCalledWith('/project/test');
344+
});
345+
229346
it('render login snap when current browser is not chrome', async () => {
230347
const eventEmitSpy = jest.spyOn(eventEmitter, 'emit');
231348
const userAgentGetter = jest.spyOn(window.navigator, 'userAgent', 'get');

packages/base/src/page/BindUser/index.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useRef } from 'react';
1+
import { useCallback, useEffect, useMemo, useRef } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { useDispatch } from 'react-redux';
44
import { Typography, Form } from 'antd';
@@ -19,7 +19,10 @@ import EmitterKey from '@actiontech/shared/lib/data/EmitterKey';
1919
import useBrowserVersionTips from '../../hooks/useBrowserVersionTips';
2020
import { LockFilled, UserFilled } from '@actiontech/icons';
2121
import useThemeStyleData from '../../hooks/useThemeStyleData';
22-
import { ROUTE_PATHS } from '@actiontech/shared/lib/data/routePaths';
22+
import {
23+
OPEN_CLOUD_BEAVER_URL_PARAM_NAME,
24+
ROUTE_PATHS
25+
} from '@actiontech/shared/lib/data/routePaths';
2326
import { LocalStorageWrapper } from '@actiontech/shared';
2427
import {
2528
StorageKey,
@@ -51,6 +54,23 @@ const BindUser = () => {
5154
return `Bearer ${token}`;
5255
};
5356

57+
const navigateToTarget = useCallback(() => {
58+
const encodedTarget = urlParams?.target;
59+
if (encodedTarget) {
60+
const decoded = decodeURIComponent(encodedTarget);
61+
const [path, targetParams] = decoded.split('?');
62+
if (targetParams) {
63+
navigate(`${path}?${targetParams}`);
64+
} else if (path.endsWith('cloud-beaver')) {
65+
navigate(`${path}?${OPEN_CLOUD_BEAVER_URL_PARAM_NAME}=true`);
66+
} else {
67+
navigate(path);
68+
}
69+
} else {
70+
navigate(ROUTE_PATHS.BASE.HOME);
71+
}
72+
}, [navigate, urlParams]);
73+
5474
const login = (values: OauthLoginFormFields) => {
5575
const oauth2Token = urlParams?.oauth2_token;
5676
loginLock.current = true;
@@ -73,7 +93,7 @@ const BindUser = () => {
7393
.then((res) => {
7494
if (res.data.code === ResponseCode.SUCCESS) {
7595
dispatch(updateToken({ token: concatToken(res.data.data?.token) }));
76-
navigate(ROUTE_PATHS.BASE.HOME);
96+
navigateToTarget();
7797
// #if [ee]
7898
LocalStorageWrapper.set(
7999
StorageKey.SHOW_COMPANY_NOTICE,
@@ -111,10 +131,11 @@ const BindUser = () => {
111131
return;
112132
}
113133
dispatch(updateToken({ token: concatToken(token) }));
114-
navigate(ROUTE_PATHS.BASE.HOME);
134+
navigateToTarget();
115135
}, [
116136
dispatch,
117137
navigate,
138+
navigateToTarget,
118139
t,
119140
urlParams?.dms_token,
120141
urlParams?.error,

packages/base/src/page/Login/__snapshots__/index.test.tsx.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ exports[`page/Login-ee render login success when has location search val render
546546
</button>
547547
<a
548548
class="ant-btn ant-btn-default other-login-btn basic-button-wrapper css-geipcv"
549-
href="/v1/dms/oauth2/link"
549+
href="/v1/dms/oauth2/link?target=%252Ftransit%253Ffrom%253Dcloudbeaver"
550550
>
551551
<span>
552552
Login With Oauth2
@@ -838,7 +838,7 @@ exports[`page/Login-ee render login success when has location search val render
838838
</button>
839839
<a
840840
class="ant-btn ant-btn-default other-login-btn basic-button-wrapper css-geipcv"
841-
href="/v1/dms/oauth2/link"
841+
href="/v1/dms/oauth2/link?target=%252Findex1"
842842
>
843843
<span>
844844
Login With Oauth2
@@ -1130,7 +1130,7 @@ exports[`page/Login-ee render login success when has location search val render
11301130
</button>
11311131
<a
11321132
class="ant-btn ant-btn-default other-login-btn basic-button-wrapper css-geipcv"
1133-
href="/v1/dms/oauth2/link"
1133+
href="/v1/dms/oauth2/link?target=%252Fproject%252F700300%252Fcloud-beaver"
11341134
>
11351135
<span>
11361136
Login With Oauth2

packages/base/src/page/Login/components/LoginForm.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
import { Form, Typography, Space, Checkbox } from 'antd';
2-
import React, { useEffect } from 'react';
2+
import React, { useEffect, useMemo } from 'react';
33
import { useTranslation } from 'react-i18next';
44
import { useRequest } from 'ahooks';
5-
import { BasicInput, BasicButton, BasicToolTip } from '@actiontech/shared';
5+
import {
6+
BasicInput,
7+
BasicButton,
8+
BasicToolTip,
9+
useTypedQuery
10+
} from '@actiontech/shared';
611
import { LockFilled, UserFilled } from '@actiontech/icons';
712
import useThemeStyleData from '../../../hooks/useThemeStyleData';
813
import { LoginFormProps } from '../types';
914
import { DmsApi } from '@actiontech/shared/lib/api';
1015
import { SystemRole } from '@actiontech/shared/lib/enum';
16+
import {
17+
DMS_REDIRECT_KEY_PARAMS_NAME,
18+
ROUTE_PATHS
19+
} from '@actiontech/shared/lib/data/routePaths';
20+
21+
const OAUTH2_LOGIN_URL = '/v1/dms/oauth2/link';
1122

1223
const LoginForm: React.FC<LoginFormProps> = ({
1324
onSubmit,
@@ -18,6 +29,7 @@ const LoginForm: React.FC<LoginFormProps> = ({
1829
const { t } = useTranslation();
1930

2031
const username = Form.useWatch('username', form);
32+
const extractQueries = useTypedQuery();
2133

2234
const { baseTheme } = useThemeStyleData();
2335

@@ -43,6 +55,16 @@ const LoginForm: React.FC<LoginFormProps> = ({
4355
}
4456
);
4557

58+
const oauth2LoginUrl = useMemo(() => {
59+
const target = extractQueries(ROUTE_PATHS.BASE.LOGIN.index)?.target;
60+
if (target) {
61+
return `${OAUTH2_LOGIN_URL}?${DMS_REDIRECT_KEY_PARAMS_NAME}=${encodeURIComponent(
62+
target
63+
)}`;
64+
}
65+
return OAUTH2_LOGIN_URL;
66+
}, [extractQueries]);
67+
4668
const renderLoginButton = () => {
4769
const disabledLoginButton =
4870
username === SystemRole.admin
@@ -152,7 +174,7 @@ const LoginForm: React.FC<LoginFormProps> = ({
152174
{/* #endif */}
153175
{renderLoginButton()}
154176
{oauthConfig?.enable_oauth2 ? (
155-
<BasicButton className="other-login-btn" href="/v1/dms/oauth2/link">
177+
<BasicButton className="other-login-btn" href={oauth2LoginUrl}>
156178
{oauthConfig?.login_tip}
157179
</BasicButton>
158180
) : null}

packages/shared/lib/data/routePaths.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const ROUTE_PATHS = {
1212
USER_BIND: {
1313
index: {
1414
path: '/user/bind',
15-
query: 'oauth2_token&error&user_exist&dms_token&refresh_token'
15+
query: 'oauth2_token&error&user_exist&dms_token&refresh_token&target'
1616
}
1717
},
1818
HOME: '/',

0 commit comments

Comments
 (0)