Skip to content

Commit 0f64b43

Browse files
authored
Merge pull request #63 from Jalen-Stephens/jalen/ui-fix-cloud
moved ui under resources and added ui to security protocol
2 parents 554c621 + bc580a5 commit 0f64b43

File tree

9 files changed

+90
-48
lines changed

9 files changed

+90
-48
lines changed

client/config.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/main/java/dev/coms4156/project/metadetect/config/SecurityConfig.java

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import org.springframework.beans.factory.annotation.Value;
77
import org.springframework.context.annotation.Bean;
88
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.core.annotation.Order;
10+
import org.springframework.http.HttpMethod;
911
import org.springframework.security.config.Customizer;
1012
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1113
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -21,7 +23,6 @@
2123
import org.springframework.web.cors.CorsConfigurationSource;
2224
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
2325

24-
2526
/**
2627
* Spring Security configuration for the MetaDetect service.
2728
* Defines authentication, authorization, and HTTP security policies.
@@ -34,20 +35,22 @@ public class SecurityConfig {
3435
* Security filter chain for API endpoints under /api/**.
3536
* Enforces JWT authentication via OAuth2 Resource Server.
3637
* Public endpoints (e.g. /api/health) are permitted without auth.
37-
*
38-
* @param http HttpSecury builder
39-
* @return configured SecurityFilterChain
4038
*/
4139
@Bean
40+
@Order(1)
4241
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
4342
http
4443
// Only apply this chain to /api/** endpoints
4544
.securityMatcher("/api/**")
4645
.csrf(csrf -> csrf.disable())
4746
.cors(Customizer.withDefaults())
4847
.authorizeHttpRequests(auth -> auth
49-
// Public API endpoints (if you have any under /api)
48+
// Allow CORS preflight (OPTIONS) through without auth
49+
.requestMatchers(HttpMethod.OPTIONS, "/api/**").permitAll()
50+
51+
// Public API endpoints
5052
.requestMatchers("/api/health", "/api/public/**").permitAll()
53+
5154
// Everything else under /api/** requires auth
5255
.anyRequest().authenticated()
5356
)
@@ -56,14 +59,52 @@ public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exce
5659
return http.build();
5760
}
5861

62+
/**
63+
* Security filter chain for non-API endpoints:
64+
* Serves static assets, HTML pages, and allows public access to the Pulse client.
65+
*/
66+
@Bean
67+
@Order(2)
68+
public SecurityFilterChain webSecurityFilterChain(HttpSecurity http) throws Exception {
69+
http
70+
.csrf(csrf -> csrf.disable())
71+
.cors(Customizer.withDefaults())
72+
.authorizeHttpRequests(auth -> auth
73+
// Public pages (served from src/main/resources/static)
74+
.requestMatchers(
75+
"/",
76+
"/index.html",
77+
"/login.html",
78+
"/signup.html",
79+
"/compose.html"
80+
).permitAll()
81+
82+
// Static assets – list concrete files and folders
83+
.requestMatchers(
84+
"/styles.css",
85+
"/compose.css",
86+
"/config.js",
87+
"/app.js",
88+
"/compose.js",
89+
"/css/**",
90+
"/js/**",
91+
"/images/**",
92+
"/fonts/**",
93+
"/webjars/**"
94+
).permitAll()
95+
96+
// Everything else (non-API) is allowed
97+
.anyRequest().permitAll()
98+
);
99+
100+
return http.build();
101+
}
102+
103+
59104
/**
60105
* JWT decoder configured to validate Supabase-issued access tokens.
61106
* Uses the project's JWT secret for HS256 signature validation
62107
* and enforces the correct issuer URL.
63-
*
64-
* @param jwtSecret Supabase project's JWT secret
65-
* @param projectBaseUrl Supabase project base URL
66-
* @return configured JwtDecoder
67108
*/
68109
@Bean
69110
JwtDecoder jwtDecoder(
@@ -91,8 +132,6 @@ JwtDecoder jwtDecoder(
91132
/**
92133
* CORS configuration allowing requests from any origin.
93134
* Allows common HTTP methods and headers.
94-
*
95-
* @return configured CorsConfigurationSource
96135
*/
97136
@Bean
98137
CorsConfigurationSource corsConfigurationSource() {
Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
(() => {
2-
const DEFAULT_CONFIG = { apiBaseUrl: 'http://localhost:8080' };
3-
const config = Object.assign({}, DEFAULT_CONFIG, window.APP_CONFIG || {});
4-
const apiBase = config.apiBaseUrl?.replace(/\/$/, '') || DEFAULT_CONFIG.apiBaseUrl;
2+
// Since frontend is now served by Spring Boot, just use same-origin API calls.
3+
const apiBase = ''; // empty prefix = same domain + same port
54
const TOKEN_STORAGE_KEY = 'pulse-demo-token';
65

76
const tabs = document.querySelectorAll('.tab');
@@ -12,7 +11,8 @@
1211
const copyButton = document.getElementById('copy-response');
1312
const instanceLabel = document.getElementById('instance-label');
1413

15-
instanceLabel.textContent = apiBase.replace(/^https?:\/\//, '');
14+
// Show current origin (e.g., meta-detect.herokuapp.com)
15+
instanceLabel.textContent = window.location.host;
1616

1717
const setMode = (mode) => {
1818
tabs.forEach((tab) => {
@@ -56,7 +56,9 @@
5656
};
5757

5858
const postJson = async (path, body) => {
59+
// path example: "/auth/login"
5960
const url = `${apiBase}${path}`;
61+
6062
const response = await fetch(url, {
6163
method: 'POST',
6264
headers: {
@@ -67,28 +69,27 @@
6769

6870
const text = await response.text();
6971
let parsed = text;
72+
7073
try {
7174
parsed = JSON.parse(text);
72-
} catch (err) {
73-
// plain text; leave as-is
74-
}
75+
} catch (_) {}
7576

7677
return { response, parsed };
7778
};
7879

7980
const saveAccessTokenIfPresent = (payload) => {
80-
if (!payload || typeof payload !== 'object') {
81-
return false;
82-
}
81+
if (!payload || typeof payload !== 'object') return false;
82+
8383
const direct = payload.access_token || payload.accessToken;
8484
const nested = payload.session?.access_token || payload.session?.accessToken;
8585
const token = direct || nested;
86+
8687
if (token) {
8788
try {
8889
localStorage.setItem(TOKEN_STORAGE_KEY, token);
8990
return true;
9091
} catch (err) {
91-
console.warn('Unable to persist token', err);
92+
console.warn('Cannot store token', err);
9293
}
9394
}
9495
return false;
@@ -97,6 +98,7 @@
9798
const handleAuthSubmit = async (event, mode) => {
9899
event.preventDefault();
99100
const form = event.currentTarget;
101+
100102
const data = new FormData(form);
101103
const email = (data.get('email') || '').toString().trim();
102104
const password = data.get('password')?.toString();
@@ -109,7 +111,7 @@
109111
if (mode === 'signup') {
110112
const confirm = data.get('confirm')?.toString();
111113
if (password !== confirm) {
112-
updateStatus('Passwords must match before continuing.', 'error');
114+
updateStatus('Passwords must match.', 'error');
113115
return;
114116
}
115117
}
@@ -120,9 +122,11 @@
120122
try {
121123
const path = mode === 'login' ? '/auth/login' : '/auth/signup';
122124
const { response, parsed } = await postJson(path, { email, password });
125+
123126
const tokenSaved = response.ok ? saveAccessTokenIfPresent(parsed) : false;
124127
const prefix = response.ok ? 'Success' : `Error ${response.status}`;
125-
const suffix = tokenSaved ? ' — access token saved for Pulse Studio' : '';
128+
const suffix = tokenSaved ? ' — token saved for Pulse Studio' : '';
129+
126130
updateStatus(`${prefix}: ${response.statusText}${suffix}`, response.ok ? 'success' : 'error');
127131
updateResponse(parsed);
128132

@@ -131,30 +135,26 @@
131135
window.location.href = './compose.html';
132136
}, 600);
133137
}
134-
} catch (error) {
135-
console.error(error);
136-
updateStatus('Network error — confirm the API is running on the configured host.', 'error');
137-
updateResponse(error.message);
138+
} catch (err) {
139+
console.error(err);
140+
updateStatus('Network error — backend is not reachable.', 'error');
141+
updateResponse(err.message);
138142
} finally {
139143
toggleFormDisabled(form, false);
140144
}
141145
};
142146

143-
loginForm.addEventListener('submit', (event) => handleAuthSubmit(event, 'login'));
144-
signupForm.addEventListener('submit', (event) => handleAuthSubmit(event, 'signup'));
147+
loginForm.addEventListener('submit', (e) => handleAuthSubmit(e, 'login'));
148+
signupForm.addEventListener('submit', (e) => handleAuthSubmit(e, 'signup'));
145149

146150
copyButton.addEventListener('click', async () => {
147151
try {
148152
await navigator.clipboard.writeText(responseOutput.textContent);
149153
copyButton.textContent = 'Copied!';
150-
setTimeout(() => {
151-
copyButton.textContent = 'Copy';
152-
}, 1500);
154+
setTimeout(() => { copyButton.textContent = 'Copy'; }, 1500);
153155
} catch (err) {
154156
copyButton.textContent = 'Unable to copy';
155-
setTimeout(() => {
156-
copyButton.textContent = 'Copy';
157-
}, 1500);
157+
setTimeout(() => { copyButton.textContent = 'Copy'; }, 1500);
158158
}
159159
});
160160

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
<link rel="preconnect" href="https://fonts.googleapis.com">
88
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
99
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600&display=swap" rel="stylesheet">
10-
<link rel="stylesheet" href="./compose.css">
11-
<script defer src="./config.js"></script>
12-
<script defer src="./compose.js"></script>
10+
11+
<!-- Static assets served from src/main/resources/static -->
12+
<link rel="stylesheet" href="/compose.css">
13+
<script defer src="/config.js"></script>
14+
<script defer src="/compose.js"></script>
1315
</head>
1416
<body>
1517
<div class="gradient-glow"></div>
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
(() => {
2-
const DEFAULT_CONFIG = { apiBaseUrl: 'http://localhost:8080' };
3-
const config = Object.assign({}, DEFAULT_CONFIG, window.APP_CONFIG || {});
4-
const apiBase = config.apiBaseUrl?.replace(/\/$/, '') || DEFAULT_CONFIG.apiBaseUrl;
2+
// Use same-origin API base (works locally and on Heroku)
3+
const apiBase = '';
54
const TOKEN_STORAGE_KEY = 'pulse-demo-token';
65

76
const fileInput = document.getElementById('file-input');
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
window.APP_CONFIG = {
2+
apiBaseUrl: "",
3+
};
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
<link rel="preconnect" href="https://fonts.googleapis.com">
88
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
99
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
10-
<link rel="stylesheet" href="./styles.css">
11-
<script defer src="./config.js"></script>
12-
<script defer src="./app.js"></script>
10+
11+
<!-- Static assets served from src/main/resources/static -->
12+
<link rel="stylesheet" href="/styles.css">
13+
<script defer src="/config.js"></script>
14+
<script defer src="/app.js"></script>
1315
</head>
1416
<body>
1517
<div class="noise"></div>

0 commit comments

Comments
 (0)