Skip to content

Commit fe7cbce

Browse files
authored
feat: add a relative path and hide projectid from url (#42)
* feat: add a relative path and hide projectid from url * make vercel work * fix nginx * use baseurl and more tests
1 parent 787a446 commit fe7cbce

File tree

6 files changed

+129
-52
lines changed

6 files changed

+129
-52
lines changed

Dockerfile

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ server {
2626
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
2727
server_name localhost;
2828

29+
rewrite ^/login/(.*)$ /$1 last;
30+
2931
location / {
3032
root /usr/share/nginx/html;
3133
index index.html;

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,6 @@
9898
"dependencies": {
9999
"typescript": "<5.1.4"
100100
}
101-
}
101+
},
102+
"homepage": "login"
102103
}

src/App.test.tsx

+95-36
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,142 @@
11
import '@testing-library/jest-dom';
2-
import React from 'react';
2+
import React, { PropsWithChildren } from 'react';
33
import { render, fireEvent, screen } from '@testing-library/react';
44
import App from './App';
5+
import packageJson from '../package.json';
6+
7+
const mockDescope = jest.fn();
8+
const mockAuthProvider = jest.fn();
59

610
jest.mock('@descope/react-sdk', () => ({
711
...jest.requireActual('@descope/react-sdk'),
8-
Descope: jest.fn(() => <div />)
12+
Descope: (props: unknown) => {
13+
mockDescope(props);
14+
return <div />;
15+
},
16+
AuthProvider: (props: PropsWithChildren<{ [key: string]: string }>) => {
17+
const { children } = props;
18+
mockAuthProvider(props);
19+
return <div>{children}</div>;
20+
}
921
}));
1022

11-
describe('App component', () => {
12-
beforeEach(() => {
13-
jest.resetModules();
14-
process.env.DESCOPE_PROJECT_ID = '';
15-
});
23+
const validProjectId = 'P2Sn0gttY5sY4Zu6WDGAAEJ4VTrv';
24+
const invalidProjectId = 'P2Qbs5l8F1kD1g2inbBktiCDumm';
25+
const baseUrl = 'https://api.descope.test';
26+
const flowId = 'test';
27+
const debug = true;
1628

17-
test('displays Welcome component when projectId is missing', async () => {
29+
describe('App component', () => {
30+
beforeAll(() => {
1831
Object.defineProperty(window, 'location', {
1932
value: {
20-
pathname: '/invalid-project-id'
33+
href: 'http://localhost:3000/',
34+
pathname: '',
35+
origin: 'http://localhost:3000',
36+
replace: jest.fn((href: string) => {
37+
window.location.href = href;
38+
})
2139
},
22-
writable: true // possibility to override
40+
writable: true
2341
});
24-
Object.assign(navigator, {
25-
clipboard: {
26-
writeText: () => undefined
42+
Object.defineProperty(navigator, 'clipboard', {
43+
value: {
44+
writeText: jest.fn()
2745
}
2846
});
47+
});
48+
49+
beforeEach(() => {
50+
jest.resetModules();
51+
process.env.DESCOPE_PROJECT_ID = '';
52+
window.location.pathname = '';
53+
window.localStorage.clear();
54+
});
55+
56+
test('displays Welcome component when projectId is missing', async () => {
57+
window.location.pathname = `/${packageJson.homepage}/${invalidProjectId}`;
2958

3059
render(<App />);
3160
expect(screen.getByTestId('welcome-component')).toBeInTheDocument();
3261
expect(screen.getByTestId('welcome-copy-component')).toBeInTheDocument();
3362
expect(screen.getByTestId('welcome-copy-icon')).toBeInTheDocument();
3463
fireEvent.click(screen.getByTestId('welcome-copy-component'));
3564
await screen.findByTestId('welcome-copied-icon');
65+
expect(screen.getByTestId('welcome-component')).toHaveTextContent(
66+
`/${packageJson.homepage}/`
67+
);
3668
});
3769

3870
test('displays Descope component when projectId is valid and part of the location', async () => {
39-
Object.defineProperty(window, 'location', {
40-
value: {
41-
pathname: '/P2Qbs5l8F1kD1g2inbBktiCDummy'
42-
},
43-
writable: true // possibility to override
44-
});
71+
window.location.pathname = `/${packageJson.homepage}/${validProjectId}`;
4572
render(<App />);
4673
expect(screen.getByTestId('descope-component')).toBeInTheDocument();
4774
});
4875

49-
test('displays Descope component when projectId is invalid and part of the location', async () => {
50-
Object.defineProperty(window, 'location', {
51-
value: {
52-
pathname: '/P2Qbs5l8F1kD1g2inbBktiCDumm'
53-
},
54-
writable: true // possibility to override
55-
});
76+
test('displays welcome component when projectId is invalid and part of the location', async () => {
77+
window.location.pathname = `/${packageJson.homepage}/${invalidProjectId}`;
5678
render(<App />);
5779
expect(screen.getByTestId('welcome-component')).toBeInTheDocument();
5880
});
5981

6082
test('displays Descope component when projectId is valid and as an env var', async () => {
61-
process.env.DESCOPE_PROJECT_ID = 'P2Qbs5l8F1kD1g2inbBktiCDummy';
83+
process.env.DESCOPE_PROJECT_ID = validProjectId;
6284
render(<App />);
6385
expect(screen.getByTestId('descope-component')).toBeInTheDocument();
6486
});
6587

66-
test('displays Descope component when projectId is invalid and as an env var', async () => {
67-
process.env.DESCOPE_PROJECT_ID = 'P2Qbs5l8F1kD1g2inbBktiCDumm';
88+
test('displays welcome component when projectId is invalid and as an env var', async () => {
89+
process.env.DESCOPE_PROJECT_ID = invalidProjectId;
6890
render(<App />);
6991
expect(screen.getByTestId('welcome-component')).toBeInTheDocument();
7092
});
7193

7294
test('displays Descope component when projectId is valid and part of the location and env', async () => {
73-
process.env.DESCOPE_PROJECT_ID = 'P2Qbs5l8F1kD1g2inbBktiCDummk';
74-
Object.defineProperty(window, 'location', {
75-
value: {
76-
pathname: '/P2Qbs5l8F1kD1g2inbBktiCDummy'
77-
},
78-
writable: true // possibility to override
79-
});
95+
process.env.DESCOPE_PROJECT_ID = validProjectId;
96+
window.location.pathname = `/${packageJson.homepage}/${validProjectId}`;
97+
render(<App />);
98+
expect(screen.getByTestId('descope-component')).toBeInTheDocument();
99+
});
100+
101+
test('that the projectid is removed from the url', async () => {
102+
window.location.pathname = `/${packageJson.homepage}/${validProjectId}`;
80103
render(<App />);
81104
expect(screen.getByTestId('descope-component')).toBeInTheDocument();
105+
expect(window.location.pathname).toBe(`/${packageJson.homepage}/`);
106+
});
107+
108+
test('that the baseUrl is the same as the origin', async () => {
109+
Object.defineProperty(window.location, 'origin', {
110+
value: baseUrl
111+
});
112+
window.location.pathname = `/${packageJson.homepage}/${validProjectId}`;
113+
render(<App />);
114+
expect(mockAuthProvider).toHaveBeenCalledWith(
115+
expect.objectContaining({ baseUrl })
116+
);
117+
});
118+
119+
test('that the flow can be customized with env', async () => {
120+
process.env.REACT_APP_DESCOPE_BASE_URL = baseUrl;
121+
process.env.DESCOPE_FLOW_ID = flowId;
122+
process.env.DESCOPE_FLOW_DEBUG = debug.toString();
123+
124+
window.location.pathname = `/${packageJson.homepage}/${validProjectId}`;
125+
render(<App />);
126+
expect(mockAuthProvider).toHaveBeenCalledWith(
127+
expect.objectContaining({ baseUrl })
128+
);
129+
expect(mockDescope).toHaveBeenCalledWith(
130+
expect.objectContaining({ debug, flowId })
131+
);
132+
});
133+
134+
test('that the flow can be customized with search params', async () => {
135+
window.location.pathname = `/${packageJson.homepage}/${validProjectId}`;
136+
window.location.search = `?debug=${debug}&flow=${flowId}`;
137+
render(<App />);
138+
expect(mockDescope).toHaveBeenCalledWith(
139+
expect.objectContaining({ debug, flowId })
140+
);
82141
});
83142
});

src/App.tsx

+18-13
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,31 @@ const projectRegex = /^P[a-zA-Z0-9]{27}$/;
88
const App = () => {
99
const [error, setError] = useState(false);
1010

11-
const baseUrl = process.env.REACT_APP_DESCOPE_BASE_URL || '';
11+
const baseUrl =
12+
process.env.REACT_APP_DESCOPE_BASE_URL || window.location.origin;
1213

1314
let projectId = '';
1415

1516
// first, take project id from env
16-
const envProjectId = process.env.DESCOPE_PROJECT_ID;
17-
if (envProjectId && projectRegex.test(envProjectId)) {
18-
projectId = envProjectId;
19-
}
17+
const envProjectId = projectRegex.exec(
18+
process.env.DESCOPE_PROJECT_ID ?? ''
19+
)?.[0];
2020

21-
// If exists in URI we will take it from the URI
21+
// If exists in URI save it in local storage
22+
// and redirect to the same page without the project id in the URI
2223
const pathnameProjectId = window.location.pathname?.split('/').at(-1) || '';
23-
if (pathnameProjectId) {
24-
if (projectRegex.test(pathnameProjectId)) {
25-
projectId = pathnameProjectId;
26-
} else {
27-
console.log(`Invalid Project ID: ${pathnameProjectId}`); // eslint-disable-line
28-
}
24+
if (pathnameProjectId && projectRegex.test(pathnameProjectId)) {
25+
window.localStorage.setItem('descope-project-id', pathnameProjectId);
26+
window.location.pathname = window.location.pathname.replace(
27+
pathnameProjectId,
28+
''
29+
);
30+
// execution will stop here and the page will reload
2931
}
3032

33+
projectId =
34+
window.localStorage.getItem('descope-project-id') ?? envProjectId ?? '';
35+
3136
const urlParams = new URLSearchParams(window.location.search);
3237

3338
const flowId =
@@ -49,7 +54,7 @@ const App = () => {
4954
/>
5055
</div>
5156
) : (
52-
<Welcome />
57+
<Welcome baseUrl={baseUrl} />
5358
)}
5459
</div>
5560
</AuthProvider>

src/components/Welcome.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import React, { useCallback } from 'react';
33
import { useCopyToClipboard } from 'usehooks-ts';
44
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
55
import CheckIcon from '@mui/icons-material/Check';
6+
import packageJson from '../../package.json';
67

7-
const Welcome = () => {
8-
const exampleText = `https://auth.descope.io/PROJECT_ID`;
8+
const Welcome = (props: { baseUrl: string }) => {
9+
const { baseUrl } = props;
10+
const exampleText = `${baseUrl}/${packageJson.homepage}/PROJECT_ID`;
911

1012
const [value, copy] = useCopyToClipboard();
1113
const onCopy = useCallback(async () => {

vercel.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"rewrites": [
3+
{
4+
"source": "/login/:path*",
5+
"destination": "/:path*"
6+
}
7+
]
8+
}

0 commit comments

Comments
 (0)