Skip to content

Commit c63b885

Browse files
committed
Merge branch 'keycloak' into 'main'
Keycloak See merge request ExplorViz/code/frontend!262
2 parents 3a55a36 + 997865d commit c63b885

File tree

12 files changed

+592
-88
lines changed

12 files changed

+592
-88
lines changed

.env

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@ VITE_SPAN_SERV_URL=http://localhost:8083
77
VITE_USER_SERV_URL=http://localhost:8084
88
VITE_VSCODE_SERV_URL=http://localhost:3000
99
VITE_COPILOT_SERV_URL=http://localhost:4300
10-
11-
# Auth0 development credentials
12-
VITE_AUTH_DISABLED_ACCESS_TOKEN=SPECIAL_TOKEN
13-
VITE_AUTH_DISABLED_PROFILE_NAME=Jessy Doe
14-
VITE_AUTH_DISABLED_NICKNAME=Jessy
15-
VITE_AUTH_DISABLED_SUB=9000
10+
VITE_KEYCLOAK_URL=http://localhost:5657
1611

1712
VITE_ONLY_SHOW_TOKEN=change-token
1813
VITE_VERSION_TAG=branch-commit
1914

20-
# Use like this: import.meta.env.VITE_* at the point where it is used (not in the import block)
15+
# KEYCLOAK
16+
VITE_ENABLE_SKIP_LOGIN=true
17+
VITE_ENABLE_DEV_LOGIN=true
18+
VITE_KEYCLOAK_REALM=explorviz
19+
VITE_KEYCLOAK_CLIENT_ID=frontend
20+
VITE_KEYCLOAK_REDIRECT_URI=http://localhost:8080/
21+
VITE_DEV_USER_SUB=9000
22+
VITE_DEV_USER_NAME=Development User
23+
VITE_DEV_USER_NICKNAME=dev-user
24+
VITE_DEV_ACCESS_TOKEN=dev-token

.env.production

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,7 @@ VITE_SPAN_SERV_URL=change-span-url
77
VITE_USER_SERV_URL=change-user-url
88
VITE_VSCODE_SERV_URL=change-vscode-url
99
VITE_COPILOT_SERV_URL=change-copilot-url
10-
11-
VITE_AUTH_DISABLED_ACCESS_TOKEN=SPECIAL_TOKEN
12-
VITE_AUTH_DISABLED_PROFILE_NAME=Jessy Doe
13-
VITE_AUTH_DISABLED_NICKNAME=change-nickname
14-
VITE_AUTH_DISABLED_SUB=9000
10+
VITE_KEYCLOAK_URL=change-keycloak-url
1511

1612
VITE_ONLY_SHOW_TOKEN=change-token
1713
VITE_VERSION_TAG=branch-commit

default.conf.template

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,21 @@ server {
9494

9595
# END VSCode
9696

97+
# BEGIN Keycloak
98+
99+
location /realms {
100+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
101+
proxy_set_header Host $host;
102+
103+
proxy_pass ${KEYCLOAK_URL};
104+
105+
proxy_http_version 1.1;
106+
proxy_set_header Upgrade $http_upgrade;
107+
proxy_set_header Connection "upgrade";
108+
}
109+
110+
# END Keycloak
111+
97112
# BEGIN Frontend
98113

99114
location / {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"html-to-image": "^1.11.13",
8484
"jquery": "^3.7.1",
8585
"jszip": "^3.10.1",
86+
"keycloak-js": "^26.2.1",
8687
"loader.js": "4.7.0",
8788
"meshline": "3.3.1",
8889
"plotly.js-dist": "^3.3.0",

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useCallback, useEffect } from 'react';
2+
import { useNavigate } from 'react-router-dom';
3+
import { useAuthStore } from '../../stores/auth';
4+
import { useLandscapeTokenStore } from '../../stores/landscape-token';
5+
6+
export default function LoginPage() {
7+
const login = useAuthStore((state) => state.login);
8+
const skipLogin = useAuthStore((state) => state.skipLogin);
9+
const isInitialized = useAuthStore((state) => state.isInitialized);
10+
const setLandscapeToken = useLandscapeTokenStore((state) => state.setToken);
11+
const navigate = useNavigate();
12+
13+
// Check if dev login button is enabled via environment variable
14+
const isDevLoginEnabled = import.meta.env.VITE_ENABLE_DEV_LOGIN === 'true';
15+
16+
// Check if automatic skip login is enabled via environment variable
17+
const isAutoSkipLoginEnabled =
18+
import.meta.env.VITE_ENABLE_SKIP_LOGIN === 'true';
19+
20+
const handleSkipLogin = useCallback(() => {
21+
skipLogin();
22+
23+
// Set default token if available
24+
// Note: user will be set after skipLogin() is called
25+
const defaultToken = import.meta.env.VITE_ONLY_SHOW_TOKEN;
26+
if (defaultToken && defaultToken !== 'change-token') {
27+
// Get user sub from store or fallback to env variable
28+
const ownerId =
29+
useAuthStore.getState().user?.sub ||
30+
import.meta.env.VITE_DEV_USER_SUB ||
31+
'9000';
32+
const defaultLandscapeToken = {
33+
value: defaultToken,
34+
ownerId: ownerId,
35+
created: Date.now(),
36+
alias: 'Development Token',
37+
sharedUsersIds: [],
38+
};
39+
setLandscapeToken(defaultLandscapeToken);
40+
navigate('/visualization');
41+
} else {
42+
navigate('/landscapes');
43+
}
44+
}, [skipLogin, setLandscapeToken, navigate]);
45+
46+
// Automatically skip login if VITE_ENABLE_SKIP_LOGIN is set
47+
useEffect(() => {
48+
if (isAutoSkipLoginEnabled && isInitialized) {
49+
handleSkipLogin();
50+
}
51+
}, [isAutoSkipLoginEnabled, isInitialized, handleSkipLogin]);
52+
53+
return (
54+
<div className="login-page-container">
55+
<div className="login-page-background">
56+
<div className="login-page-background-overlay"></div>
57+
</div>
58+
<div className="login-page-content">
59+
{!isInitialized ? (
60+
<div className="login-page-card login-page-card-loading">
61+
<div className="login-page-loading-content">
62+
<div className="login-page-loading-spinner"></div>
63+
<p className="login-page-loading-text">
64+
Connecting to Keycloak...
65+
</p>
66+
</div>
67+
</div>
68+
) : (
69+
<div className="login-page-card">
70+
<div className="login-page-card-header">
71+
<div className="login-page-logo">
72+
<img
73+
src="images/explorviz-logo.png"
74+
alt="ExplorViz"
75+
className="login-page-logo-image"
76+
/>
77+
</div>
78+
<h1 className="login-page-title">Welcome to ExplorViz</h1>
79+
<p className="login-page-subtitle">
80+
Please sign in to access your visualization workspace
81+
</p>
82+
</div>
83+
<div className="login-page-card-body">
84+
<button
85+
className="login-page-button login-page-button-primary"
86+
onClick={login}
87+
type="button"
88+
>
89+
<span className="login-page-button-label">
90+
Sign In / Register
91+
</span>
92+
<span className="login-page-button-ripple"></span>
93+
</button>
94+
{isDevLoginEnabled && (
95+
<button
96+
className="login-page-button login-page-button-secondary"
97+
onClick={handleSkipLogin}
98+
type="button"
99+
>
100+
<span className="login-page-button-label">
101+
Skip Login (Dev Mode)
102+
</span>
103+
<span className="login-page-button-ripple"></span>
104+
</button>
105+
)}
106+
</div>
107+
</div>
108+
)}
109+
</div>
110+
</div>
111+
);
112+
}

src/components/page-setup/navbar.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,7 @@ export default function Navbar() {
8282
return import.meta.env.VITE_VERSION_TAG;
8383
};
8484

85-
const logout = () => {
86-
console.log('The logout function is not available at the moment');
87-
};
85+
const logout = useAuthStore((state) => state.logout);
8886

8987
const goToLandscapeSelection = () => {
9088
setSnapshotSelected(false);
@@ -237,8 +235,19 @@ export default function Navbar() {
237235
<button
238236
className="dropdown-item"
239237
type="button"
240-
disabled
241-
onClick={() => logout}
238+
onClick={() => {
239+
window.location.href = `${import.meta.env.VITE_KEYCLOAK_URL}/realms/${import.meta.env.VITE_KEYCLOAK_REALM}/account/`;
240+
}}
241+
>
242+
<PersonIcon size="small" />
243+
Manage Account
244+
</button>
245+
</li>
246+
<li>
247+
<button
248+
className="dropdown-item"
249+
type="button"
250+
onClick={logout}
242251
>
243252
<SignOutIcon size="small" />
244253
Logout

src/pages/application.tsx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
import LoginPage from 'explorviz-frontend/src/components/page-setup/login-page';
12
import Navbar from 'explorviz-frontend/src/components/page-setup/navbar';
23
import ToastMessage from 'explorviz-frontend/src/components/page-setup/toast-message';
34
import { useAuthStore } from 'explorviz-frontend/src/stores/auth';
4-
import {
5-
useLandscapeTokenStore
6-
} from 'explorviz-frontend/src/stores/landscape-token';
5+
import { useLandscapeTokenStore } from 'explorviz-frontend/src/stores/landscape-token';
76
import { useSnapshotTokenStore } from 'explorviz-frontend/src/stores/snapshot-token';
87
import { useInitNavigation } from 'explorviz-frontend/src/stores/store-router';
98
import { useEffect, useState } from 'react';
@@ -32,6 +31,30 @@ export default function Application() {
3231

3332
useInitNavigation();
3433

34+
// Handle skip login token setup
35+
useEffect(() => {
36+
const isSkipLoginEnabled = import.meta.env.VITE_ENABLE_SKIP_LOGIN === 'true';
37+
38+
if (isSkipLoginEnabled && user) {
39+
// Set default token if available
40+
const defaultToken = import.meta.env.VITE_ONLY_SHOW_TOKEN;
41+
if (defaultToken && defaultToken !== 'change-token') {
42+
const defaultLandscapeToken = {
43+
value: defaultToken,
44+
ownerId: user.sub,
45+
created: Date.now(),
46+
alias: 'Development Token',
47+
sharedUsersIds: [],
48+
};
49+
setLandscapeToken(defaultLandscapeToken);
50+
// Only navigate if we're not already on a route
51+
if (window.location.pathname === '/') {
52+
navigate('/visualization');
53+
}
54+
}
55+
}
56+
}, [user, setLandscapeToken, navigate]);
57+
3558
// Auto-select landscape
3659
useEffect(() => {
3760
const autoSelectLandscape = async () => {
@@ -90,6 +113,13 @@ export default function Application() {
90113
}
91114
}, [searchParams, user]);
92115

116+
const isInitialized = useAuthStore((state) => state.isInitialized);
117+
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
118+
119+
if (!isInitialized || !isAuthenticated) {
120+
return <LoginPage />;
121+
}
122+
93123
return (
94124
<>
95125
<div id="ember-right-click-menu-wormhole"></div>

0 commit comments

Comments
 (0)