Skip to content

Commit 59b5320

Browse files
committed
fix: MAPBOX token usage and auth redirects
1 parent 7366e15 commit 59b5320

File tree

7 files changed

+76
-12
lines changed

7 files changed

+76
-12
lines changed

compose/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ POSTGRES_PORT=5432
1919
# -----------------------------
2020

2121
# To use the interactive Network map, a mapbox token is needed
22-
VITE_MAPBOX_TOKEN=your_mapbox_token_here
22+
MAPBOX_TOKEN=your_mapbox_token_here
2323

2424
# Authentication Configuration (Optional)
2525
# ---------------------------------------

compose/compose.dev.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ services:
4848
- REDIS_URL=redis://redis:6379/0
4949
- SERVE_FRONTEND=${SERVE_FRONTEND:-false}
5050
- DEBUG=true
51+
# Map configuration
52+
- MAPBOX_TOKEN=${MAPBOX_TOKEN}
5153
# Authentication
5254
- ENABLE_AUTH=${ENABLE_AUTH:-false}
5355
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:-}

compose/compose.full.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ services:
4444
environment:
4545
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST:-postgres}:${POSTGRES_PORT:-5432}/${POSTGRES_DB}
4646
- REDIS_URL=redis://redis:6379/0
47+
# Map configuration
48+
- MAPBOX_TOKEN=${MAPBOX_TOKEN}
4749
# Authentication
4850
- ENABLE_AUTH=${ENABLE_AUTH}
4951
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}

frontend/app/src/routes/+layout.svelte

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,12 @@
5454
5555
// If auth is enabled and user is not authenticated, redirect to login
5656
// (except if already on login page)
57+
// Note: The API client's automatic 401 redirect skips /auth/ endpoints
58+
// to prevent redirect loops, so we handle the redirect manually here
5759
const currentPath = $page.url.pathname;
58-
if (!authStore.loading && !authStore.isAuthenticated && authStore.error === null) {
59-
// Only redirect if we got a 401/400 response (auth is enabled)
60-
// If auth is disabled, we won't redirect
60+
if (!authStore.loading && !authStore.isAuthenticated) {
6161
if (currentPath !== '/login') {
62-
// Try to make a test request to see if auth is required
63-
// The API client will handle the redirect if needed
62+
goto('/login');
6463
}
6564
}
6665
});

frontend/map/src/App.jsx

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import React, { useState, useEffect } from 'react';
22
import { Routes, Route, useSearchParams, useNavigate } from 'react-router-dom';
33
import KeplerMap from './KeplerMap';
44

5-
// Get Mapbox token from environment variable
6-
const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_TOKEN || '';
7-
85
function NetworkMap() {
96
const [searchParams] = useSearchParams();
107
const navigate = useNavigate();
@@ -13,17 +10,64 @@ function NetworkMap() {
1310
const [config, setConfig] = useState({});
1411
const [loading, setLoading] = useState(true);
1512
const [error, setError] = useState(null);
13+
const [mapboxToken, setMapboxToken] = useState('');
1614

1715
// Back URL
1816
const backUrl = id
1917
? (import.meta.env.DEV ? `http://localhost:5173/network?id=${id}` : `/network?id=${id}`)
2018
: '/network';
2119

20+
// Login URL - in dev mode, the SvelteKit app runs on port 5173
21+
const loginUrl = import.meta.env.DEV ? 'http://localhost:5173/login' : '/login';
22+
23+
// Check authentication on mount
24+
useEffect(() => {
25+
async function checkAuth() {
26+
try {
27+
const response = await fetch('/api/v1/auth/me');
28+
// If we get 401, redirect to login (auth is enabled and user not authenticated)
29+
if (response.status === 401) {
30+
window.location.href = loginUrl;
31+
}
32+
} catch (err) {
33+
// Network errors - continue anyway (auth might be disabled)
34+
console.warn('Auth check failed:', err);
35+
}
36+
}
37+
38+
checkAuth();
39+
}, []);
40+
41+
useEffect(() => {
42+
// Fetch mapbox token from backend config
43+
async function fetchMapConfig() {
44+
try {
45+
const response = await fetch('/api/v1/map/config');
46+
if (response.ok) {
47+
const data = await response.json();
48+
setMapboxToken(data.mapbox_token || '');
49+
}
50+
} catch (err) {
51+
console.warn('Failed to fetch map config:', err);
52+
// Token will remain empty, map will show warning
53+
}
54+
}
55+
56+
fetchMapConfig();
57+
}, []);
58+
2259
useEffect(() => {
2360
// Poll task status until complete
2461
async function pollTaskStatus(statusUrl, maxAttempts = 60) {
2562
for (let i = 0; i < maxAttempts; i++) {
2663
const response = await fetch(statusUrl);
64+
65+
// Check for auth errors
66+
if (response.status === 401) {
67+
window.location.href = loginUrl;
68+
return;
69+
}
70+
2771
const result = await response.json();
2872

2973
if (result.state === 'SUCCESS') {
@@ -43,6 +87,11 @@ function NetworkMap() {
4387
async function fetchData(endpoint, dataType) {
4488
const response = await fetch(endpoint);
4589
if (!response.ok) {
90+
if (response.status === 401) {
91+
// User not authenticated - redirect to login
92+
window.location.href = loginUrl;
93+
return;
94+
}
4695
if (response.status === 404) {
4796
throw new Error(`Network "${id}" not found`);
4897
}
@@ -209,14 +258,14 @@ function NetworkMap() {
209258
);
210259
}
211260

212-
if (!MAPBOX_TOKEN) {
261+
if (!mapboxToken) {
213262
return (
214263
<div style={styles.center}>
215264
<div style={styles.warning}>
216265
<h2>Mapbox Token Required</h2>
217266
<p>To display the network map, configure a Mapbox access token in your environment.</p>
218267
<p style={styles.small}>
219-
Set <code>VITE_MAPBOX_TOKEN</code> in your <code>.env</code> file.
268+
Set <code>MAPBOX_TOKEN</code> environment variable when running the application.
220269
</p>
221270
<p style={styles.small}>
222271
Create a free account and obtain a token at <a href="https://www.mapbox.com/" target="_blank" rel="noopener">mapbox.com</a>
@@ -238,7 +287,7 @@ function NetworkMap() {
238287
<KeplerMap
239288
datasets={datasets}
240289
config={config}
241-
mapboxToken={MAPBOX_TOKEN}
290+
mapboxToken={mapboxToken}
242291
/>
243292
</div>
244293
</div>

src/pypsa_app/backend/api/routes/map.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@
66
from pypsa_app.backend.api.utils.task_utils import queue_task
77
from pypsa_app.backend.models import Network, User
88
from pypsa_app.backend.schemas.common import TaskResponse
9+
from pypsa_app.backend.settings import settings
910
from pypsa_app.backend.tasks import extract_geographic_layer_task
1011

1112
router = APIRouter()
1213
logger = logging.getLogger(__name__)
1314

1415

16+
@router.get("/config")
17+
def get_map_config():
18+
"""Get map configuration including Mapbox token"""
19+
return {
20+
"mapbox_token": settings.mapbox_token or "",
21+
}
22+
23+
1524
@router.get("/{network_id}/buses", response_model=TaskResponse)
1625
def get_buses(
1726
network: Network = Depends(get_network_or_404),

src/pypsa_app/backend/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,8 @@ def networks_path(self) -> Path:
6363
session_cookie_name: str = "pypsa_session"
6464
session_ttl: int = 604800 # 7 days in seconds
6565

66+
# Map configuration
67+
mapbox_token: str | None = None
68+
6669

6770
settings = Settings()

0 commit comments

Comments
 (0)