Skip to content

Commit f2ae337

Browse files
authored
Merge pull request #689 from actiontech/feature/dms-ee-issue-627
[feature]: support redirecting to the target page after successful oauth2 login on the login page
2 parents d55b70e + 01244c9 commit f2ae337

File tree

6 files changed

+173
-13
lines changed

6 files changed

+173
-13
lines changed

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
@@ -102,7 +102,7 @@ describe('page/BindUser-ee', () => {
102102
);
103103
});
104104

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

156263
describe('render url have search val', () => {
@@ -210,6 +317,16 @@ describe('page/BindUser-ee', () => {
210317
});
211318
});
212319

320+
it('render oauth2_token params submit with target param', async () => {
321+
const search = `dms_token=oauth2_token_val&user_exist=true&refresh_token=id_token_val&target=${encodeURIComponent(
322+
'/project/test'
323+
)}`;
324+
customRender(`/user/bind?${search}`);
325+
await act(async () => jest.advanceTimersByTime(300));
326+
expect(navigateSpy).toHaveBeenCalled();
327+
expect(navigateSpy).toHaveBeenCalledWith('/project/test');
328+
});
329+
213330
it('render login snap when current browser is not chrome', async () => {
214331
const eventEmitSpy = jest.spyOn(eventEmitter, 'emit');
215332
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;
@@ -72,7 +92,7 @@ const BindUser = () => {
7292
.then((res) => {
7393
if (res.data.code === ResponseCode.SUCCESS) {
7494
dispatch(updateToken({ token: concatToken(res.data.data?.token) }));
75-
navigate(ROUTE_PATHS.BASE.HOME);
95+
navigateToTarget();
7696
// #if [ee]
7797
LocalStorageWrapper.set(
7898
StorageKey.SHOW_COMPANY_NOTICE,
@@ -108,10 +128,11 @@ const BindUser = () => {
108128
return;
109129
}
110130
dispatch(updateToken({ token: concatToken(token) }));
111-
navigate(ROUTE_PATHS.BASE.HOME);
131+
navigateToTarget();
112132
}, [
113133
dispatch,
114134
navigate,
135+
navigateToTarget,
115136
t,
116137
urlParams?.dms_token,
117138
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&id_token'
15+
query: 'oauth2_token&error&user_exist&dms_token&id_token&target'
1616
}
1717
},
1818
HOME: '/',

0 commit comments

Comments
 (0)