Skip to content

Commit 5685326

Browse files
authored
Merge pull request #72 from data-for-change/63-implement-use-authentication-with-google-service
63 implement use authentication with google service
2 parents becb630 + 8240ede commit 5685326

5 files changed

Lines changed: 107 additions & 41 deletions

File tree

public/close-popup.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
</head>
66
<body>
77
<script>
8-
// This page is reached after the backend redirect cycle (/sd-callback/google -> redirect_url)
9-
// It simply closes the popup window. The main window will detect it's closed and refresh.
10-
window.close();
8+
// Send a message to the main application window that login was successful
9+
if (window.opener) {
10+
window.opener.postMessage('login-success', window.location.origin);
11+
}
12+
// Close the popup after a tiny delay to ensure message is sent
13+
setTimeout(() => {
14+
window.close();
15+
}, 100);
1116
</script>
1217
</body>
1318
</html>

src/App.tsx

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const RecommendationsPage = lazy(() => import('./pages/RecommendationsPage'));
2020
const Login = lazy(() => import('./components/auth/Login'));
2121
const Register = lazy(() => import('./components/auth/Register'));
2222
const Profile = lazy(() => import('./components/auth/Profile'));
23+
const LoginPopupRedirect = lazy(() => import('./components/auth/LoginPopupRedirect'));
2324

2425
const styles = {
2526
app: {
@@ -78,28 +79,29 @@ function App() {
7879
}, [dispatch]);
7980

8081
return (
81-
<DirectionProvider>
82-
<BrowserRouter>
83-
<GoogleAnalyticsTracker />
84-
<div style={{ height: "100%" }}>
85-
<Header title="Safety Data" />
86-
<div style={styles.app}>
87-
<Routes>
88-
<Route path="/" element={<HomePage />} />
89-
<Route path="/city" element={<CityPage />} />
90-
<Route path="/model" element={<ModelPage />} />
91-
<Route path="/recommend" element={<RecommendationsPage />} />
92-
<Route path="/map" element={<MapWithClusters />} />
93-
<Route path="/about" element={<AboutPage />} />
94-
<Route path="/login" element={<Login />} />
95-
<Route path="/register" element={<Register />} />
96-
<Route path="/profile" element={<Profile />} />
97-
</Routes>
98-
</div>
99-
<Footer />
100-
</div>
101-
</BrowserRouter>
102-
</DirectionProvider>
82+
<DirectionProvider>
83+
<BrowserRouter>
84+
<GoogleAnalyticsTracker />
85+
<div style={{ height: '100%' }}>
86+
<Header title='Safety Data' />
87+
<div style={styles.app}>
88+
<Routes>
89+
<Route path='/' element={<HomePage />} />
90+
<Route path='/city' element={<CityPage />} />
91+
<Route path='/model' element={<ModelPage />} />
92+
<Route path='/recommend' element={<RecommendationsPage />} />
93+
<Route path='/map' element={<MapWithClusters />} />
94+
<Route path='/about' element={<AboutPage />} />
95+
<Route path='/login' element={<Login />} />
96+
<Route path='/register' element={<Register />} />
97+
<Route path='/profile' element={<Profile />} />
98+
<Route path='/login-popup-redirect' element={<LoginPopupRedirect />} />
99+
</Routes>
100+
</div>
101+
<Footer />
102+
</div>
103+
</BrowserRouter>
104+
</DirectionProvider>
103105
);
104106
}
105107

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React, { useEffect } from 'react';
2+
3+
const LoginPopupRedirect: React.FC = () => {
4+
useEffect(() => {
5+
// Send the success message to the original window (opener)
6+
if (window.opener) {
7+
window.opener.postMessage('login-success', window.location.origin);
8+
}
9+
10+
// Close the popup after a brief delay
11+
const timer = setTimeout(() => {
12+
window.close();
13+
}, 500);
14+
15+
return () => clearTimeout(timer);
16+
}, []);
17+
18+
return (
19+
<div style={{
20+
display: 'flex',
21+
justifyContent: 'center',
22+
alignItems: 'center',
23+
height: '100vh',
24+
fontFamily: 'sans-serif'
25+
}}>
26+
<h4>עוד רגע... מתחברים</h4>
27+
</div>
28+
);
29+
};
30+
31+
export default LoginPopupRedirect;

src/services/AuthService.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import axios, { AxiosResponse } from 'axios';
2-
import { API_ANYWAY_URL } from '../utils/globalEnvs';
2+
import { API_URL } from '../utils/globalEnvs';
33
import { IUserLoggedIn, IUser } from '../types/User';
44

55
class AuthService {
6-
apiUrl = API_ANYWAY_URL;
6+
apiUrl = API_URL;
77

88
// Safety Data session-based endpoints
99
isLoggedIn = async (): Promise<AxiosResponse<IUserLoggedIn>> => {
@@ -24,10 +24,8 @@ class AuthService {
2424
* GET /sd-authorize/google
2525
*/
2626
getAuthorizeUrl = (redirectUrl?: string) => {
27-
const url = new URL(`https://www.anyway.co.il/sd-authorize/google`);
28-
if (redirectUrl) {
29-
url.searchParams.append('redirect_url', redirectUrl);
30-
}
27+
// redirect url to safety-data-client
28+
const url = new URL(`${this.apiUrl}/sd-authorize/google?redirect_url=${redirectUrl}`);
3129
return url.toString();
3230
};
3331
}

src/stores/user/UserStore.ts

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,31 +77,61 @@ export default class UserStore {
7777
* Documentation Flow: Initiates the redirect to the backend auth endpoint.
7878
* We use a popup window to handle the redirect cycle so the user doesn't leave the current page.
7979
*/
80+
private popupWindow: Window | null = null;
81+
8082
login() {
8183
const width = 500;
82-
const height = 600;
84+
const height = 650;
8385
const left = window.screenX + (window.outerWidth - width) / 2;
8486
const top = window.screenY + (window.outerHeight - height) / 2;
87+
const name = 'Google Authentication';
88+
const strWindowFeatures = `toolbar=no, menubar=no, width=${width}, height=${height}, top=${top}, left=${left}, status=no, resizable=yes, scrollbars=yes`;
8589

86-
// Use the current origin as the redirect URL for the backend to send the user back to
87-
const redirectUrl = window.location.origin + '/close-popup.html';
90+
// Use the specific trusted route name from the Anyway app config
91+
const redirectUrl = window.location.origin + '/login-popup-redirect';
8892
const authUrl = this.authService.getAuthorizeUrl(redirectUrl);
8993

90-
const popup = window.open(
91-
authUrl,
92-
'googleLogin',
93-
`width=${width},height=${height},left=${left},top=${top},status=no,resizable=yes,toolbar=no,menubar=no,scrollbars=yes`
94-
);
94+
// Remove any existing event listeners before adding a new one
95+
window.removeEventListener('message', this.handleAuthMessage);
96+
window.addEventListener('message', this.handleAuthMessage);
97+
98+
if (this.popupWindow === null || this.popupWindow.closed) {
99+
this.popupWindow = window.open(authUrl, name, strWindowFeatures);
100+
} else {
101+
this.popupWindow.focus();
102+
if (this.popupWindow.location.origin !== window.location.origin) {
103+
this.popupWindow.location.href = authUrl;
104+
}
105+
}
95106

96-
// Check if popup is closed and then refresh auth status
107+
// Fallback: Check if popup was closed manually
97108
const checkPopup = setInterval(() => {
98-
if (!popup || popup.closed) {
109+
if (!this.popupWindow || this.popupWindow.closed) {
99110
clearInterval(checkPopup);
100111
this.checkAuthStatus();
101112
}
102113
}, 1000);
103114
}
104115

116+
private handleAuthMessage = (event: MessageEvent) => {
117+
// Verify the origin for security as per provided Anyway app code
118+
if (event.origin !== window.location.origin) {
119+
console.warn('Authentication redirect origin is not valid');
120+
return;
121+
}
122+
123+
if (event.data === 'login-success') {
124+
console.info('authentication success!, redirect to main page...');
125+
if (this.popupWindow) {
126+
this.popupWindow.close();
127+
}
128+
window.removeEventListener('message', this.handleAuthMessage);
129+
130+
// Refresh the app to ensure all stores and cookies are in sync
131+
window.location.pathname = '/';
132+
}
133+
};
134+
105135
async logout() {
106136
try {
107137
await this.authService.logout();

0 commit comments

Comments
 (0)