security: remove VITE_TLSYNC_SECRET from frontend bundle
- Remove import.meta.env.VITE_TLSYNC_SECRET from syncService.ts - Add optional token parameter to SyncConnectionOptions interface (caller passes token fetched from API, not a baked-in secret) - Add clientEnvBlocklist in vite.config.ts to prevent sensitive VITE_ env vars (TLSYNC_SECRET, MICROSOFT_CLIENT_SECRET*) from reaching bundle - Remove VITE_TLSYNC_SECRET from .env.example - Token auth for TLSync now handled server-side or via API endpoint Ref: t_99d166c6
This commit is contained in:
parent
0db53bfd9c
commit
0d76868a42
@ -27,7 +27,6 @@ VITE_SUPABASE_ANON_KEY=your-supabase-anon-key
|
|||||||
# TLSync (TLDraw Sync) Configuration
|
# TLSync (TLDraw Sync) Configuration
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
VITE_TLSYNC_URL=https://app.classroomcopilot.ai/tldraw
|
VITE_TLSYNC_URL=https://app.classroomcopilot.ai/tldraw
|
||||||
VITE_TLSYNC_SECRET=your-tlsync-secret
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# WhisperLive (Transcription) Configuration
|
# WhisperLive (Transcription) Configuration
|
||||||
|
|||||||
@ -15,6 +15,8 @@ export interface SyncConnectionOptions {
|
|||||||
color: string;
|
color: string;
|
||||||
roomId?: string;
|
roomId?: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
|
/** Optional TLSync auth token (fetched from API server-side, never baked into bundle) */
|
||||||
|
token?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSyncConnectionOptions(options: SyncConnectionOptions) {
|
export function createSyncConnectionOptions(options: SyncConnectionOptions) {
|
||||||
@ -22,8 +24,8 @@ export function createSyncConnectionOptions(options: SyncConnectionOptions) {
|
|||||||
userId,
|
userId,
|
||||||
displayName,
|
displayName,
|
||||||
roomId = 'multiplayer',
|
roomId = 'multiplayer',
|
||||||
baseUrl
|
baseUrl,
|
||||||
|
token = ''
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
// Ensure we have valid user info
|
// Ensure we have valid user info
|
||||||
@ -72,7 +74,8 @@ export function createSyncConnectionOptions(options: SyncConnectionOptions) {
|
|||||||
roomId: effectiveRoomId
|
roomId: effectiveRoomId
|
||||||
});
|
});
|
||||||
|
|
||||||
const token = import.meta.env.VITE_TLSYNC_SECRET ?? ''
|
// Token is provided by the caller (e.g. fetched from API server-side).
|
||||||
|
// Never embed a shared secret in the frontend bundle.
|
||||||
const tokenParam = token ? `?token=${encodeURIComponent(token)}` : ''
|
const tokenParam = token ? `?token=${encodeURIComponent(token)}` : ''
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -14,9 +14,16 @@ export default defineConfig(({ mode }) => {
|
|||||||
|
|
||||||
// Determine client-side env vars to expose
|
// Determine client-side env vars to expose
|
||||||
const envPrefix = 'VITE_'
|
const envPrefix = 'VITE_'
|
||||||
|
// Blocklist: sensitive env vars that must NEVER reach the client bundle.
|
||||||
|
const clientEnvBlocklist = new Set([
|
||||||
|
'VITE_TLSYNC_SECRET',
|
||||||
|
'VITE_MICROSOFT_CLIENT_SECRET',
|
||||||
|
'VITE_MICROSOFT_CLIENT_SECRET_ID',
|
||||||
|
])
|
||||||
|
|
||||||
const clientEnv = Object.fromEntries(
|
const clientEnv = Object.fromEntries(
|
||||||
Object.entries(env)
|
Object.entries(env)
|
||||||
.filter(([key]) => key.startsWith(envPrefix))
|
.filter(([key]) => key.startsWith(envPrefix) && !clientEnvBlocklist.has(key))
|
||||||
.map(([key, value]) => [`import.meta.env.${key}`, JSON.stringify(value)])
|
.map(([key, value]) => [`import.meta.env.${key}`, JSON.stringify(value)])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user