Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/app/api/auth/check/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { NextResponse } from 'next/server';
import { configLoader } from '@/lib/config/configLoader';

export async function GET() {
try {
// Load config to check if password is set
const config = await configLoader.loadConfig();
const dashboardPassword = config.global?.server?.dashboardPassword;

return NextResponse.json({
passwordRequired: !!dashboardPassword && dashboardPassword.length > 0,
});
} catch (error) {
console.error('Failed to check auth status:', error);
return NextResponse.json({ passwordRequired: false });
}
}
3 changes: 2 additions & 1 deletion src/app/api/auth/logout/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { NextRequest, NextResponse } from 'next/server';
export async function POST(_request: NextRequest) {
const response = NextResponse.json({ success: true });

// Delete the auth cookie
// Delete the auth cookies
response.cookies.delete('auth-token');
response.cookies.delete('password-required');

return response;
}
42 changes: 42 additions & 0 deletions src/components/AuthCheck.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import { useEffect } from 'react';
import { useRouter } from 'next/navigation';

export function AuthCheck() {
const router = useRouter();

useEffect(() => {
const checkAuth = async () => {
try {
// Check if password is required
const response = await fetch('/api/auth/check');
const data = await response.json();

// Set cookie to indicate if password is required
if (data.passwordRequired) {
document.cookie = 'password-required=true; path=/; max-age=86400'; // 24 hours

Check warning

Code scanning / CodeQL

Clear text transmission of sensitive cookie Medium

Sensitive cookie sent without enforcing SSL encryption.

Copilot Autofix

AI 5 months ago

To fix the problem, we need to ensure that any sensitive cookies set in the code, like password-required, include the secure attribute so they are only sent over HTTPS connections. Specifically, in src/components/AuthCheck.tsx, two places set or update the password-required cookie (lines 18 and 31). We should add the string ; secure to the cookie value in both cases. This change can be safely made by appending ; secure to the cookie string in both assignments to document.cookie. No new imports or methods are needed.

Suggested changeset 1
src/components/AuthCheck.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/components/AuthCheck.tsx b/src/components/AuthCheck.tsx
--- a/src/components/AuthCheck.tsx
+++ b/src/components/AuthCheck.tsx
@@ -15,7 +15,7 @@
 
         // Set cookie to indicate if password is required
         if (data.passwordRequired) {
-          document.cookie = 'password-required=true; path=/; max-age=86400'; // 24 hours
+          document.cookie = 'password-required=true; path=/; max-age=86400; secure'; // 24 hours
 
           // Check if we have a valid auth token
           const authToken = document.cookie
@@ -28,7 +28,7 @@
           }
         } else {
           // No password required, clear the cookie
-          document.cookie = 'password-required=false; path=/; max-age=86400';
+          document.cookie = 'password-required=false; path=/; max-age=86400; secure';
         }
       } catch (error) {
         console.error('Failed to check auth status:', error);
EOF
@@ -15,7 +15,7 @@

// Set cookie to indicate if password is required
if (data.passwordRequired) {
document.cookie = 'password-required=true; path=/; max-age=86400'; // 24 hours
document.cookie = 'password-required=true; path=/; max-age=86400; secure'; // 24 hours

// Check if we have a valid auth token
const authToken = document.cookie
@@ -28,7 +28,7 @@
}
} else {
// No password required, clear the cookie
document.cookie = 'password-required=false; path=/; max-age=86400';
document.cookie = 'password-required=false; path=/; max-age=86400; secure';
}
} catch (error) {
console.error('Failed to check auth status:', error);
Copilot is powered by AI and may make mistakes. Always verify output.

// Check if we have a valid auth token
const authToken = document.cookie
.split('; ')
.find(row => row.startsWith('auth-token='));

if (!authToken) {
// No auth token, redirect to login
router.push('/login?redirect=' + encodeURIComponent(window.location.pathname));
}
} else {
// No password required, clear the cookie
document.cookie = 'password-required=false; path=/; max-age=86400';

Check warning

Code scanning / CodeQL

Clear text transmission of sensitive cookie Medium

Sensitive cookie sent without enforcing SSL encryption.

Copilot Autofix

AI 5 months ago

To fix the problem, we should append Secure to the cookie string whenever it is set, ensuring that browsers only send this cookie over HTTPS connections. In the context of the file src/components/AuthCheck.tsx, this involves updating line 31 where the cookie is being set, so that 'password-required=false; path=/; max-age=86400' becomes 'password-required=false; path=/; max-age=86400; Secure'. No new imports or methods are needed, as this is a simple string change and document.cookie supports this attribute natively.


Suggested changeset 1
src/components/AuthCheck.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/components/AuthCheck.tsx b/src/components/AuthCheck.tsx
--- a/src/components/AuthCheck.tsx
+++ b/src/components/AuthCheck.tsx
@@ -28,7 +28,7 @@
           }
         } else {
           // No password required, clear the cookie
-          document.cookie = 'password-required=false; path=/; max-age=86400';
+          document.cookie = 'password-required=false; path=/; max-age=86400; Secure';
         }
       } catch (error) {
         console.error('Failed to check auth status:', error);
EOF
@@ -28,7 +28,7 @@
}
} else {
// No password required, clear the cookie
document.cookie = 'password-required=false; path=/; max-age=86400';
document.cookie = 'password-required=false; path=/; max-age=86400; Secure';
}
} catch (error) {
console.error('Failed to check auth status:', error);
Copilot is powered by AI and may make mistakes. Always verify output.
}
} catch (error) {
console.error('Failed to check auth status:', error);
}
};

checkAuth();
}, [router]);

return null; // This component doesn't render anything
}
4 changes: 3 additions & 1 deletion src/components/ConfigProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ export default function ConfigProvider({ children }: { children: React.ReactNode
server: {
dashboardPassword: "",
dashboardPort: 3000,
websocketPort: 8080
websocketPort: 8080,
useRemoteWebSocket: false,
websocketHost: null
}
},
version: "1.1.0"
Expand Down
8 changes: 6 additions & 2 deletions src/components/dashboard-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Separator } from "@/components/ui/separator"
import { Button } from "@/components/ui/button"
import { LogOut } from "lucide-react"
import { useConfig } from "@/components/ConfigProvider"
import { AuthCheck } from "@/components/AuthCheck"

interface DashboardLayoutProps {
children: React.ReactNode
Expand Down Expand Up @@ -42,7 +43,9 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
};

return (
<SidebarProvider>
<>
<AuthCheck />
<SidebarProvider>
<AppSidebar />
<SidebarInset>
<header className="flex h-12 shrink-0 items-center gap-2 border-b px-4">
Expand Down Expand Up @@ -123,6 +126,7 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
{children}
</main>
</SidebarInset>
</SidebarProvider>
</SidebarProvider>
</>
)
}
6 changes: 3 additions & 3 deletions src/hooks/useWebSocketUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ export function useWebSocketUrl() {
fetch('/api/config')
.then(res => res.json())
.then(data => {
const port = data.config?.global?.server?.websocketPort || 8080;
const useRemoteWebSocket = data.config?.global?.server?.useRemoteWebSocket || false;
const configHost = data.config?.global?.server?.websocketHost;
const port = data.global?.server?.websocketPort || 8080;
const useRemoteWebSocket = data.global?.server?.useRemoteWebSocket || false;
const configHost = data.global?.server?.websocketHost;

// Determine the host based on configuration
let host = 'localhost'; // default
Expand Down
7 changes: 7 additions & 0 deletions src/lib/config/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ export const DEFAULT_CONFIG: Config = {
paperMode: true,
positionMode: 'HEDGE',
maxOpenPositions: 10,
server: {
dashboardPassword: '',
dashboardPort: 3000,
websocketPort: 8080,
useRemoteWebSocket: false,
websocketHost: null,
},
},
version: DEFAULT_CONFIG_VERSION,
};
Expand Down
12 changes: 12 additions & 0 deletions src/lib/config/migrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,18 @@ export function addMissingFields(userConfig: any, defaultConfig: any): any {
if (!(field in result.global)) {
result.global[field] = defaultConfig.global[field];
console.log(`Added missing global field: ${field}`);
} else if (field === 'server' && typeof defaultConfig.global[field] === 'object' && typeof result.global[field] === 'object') {
// Merge server config nested fields
const defaultServer = defaultConfig.global.server;
const userServer = result.global.server || {};
result.global.server = { ...defaultServer, ...userServer };

// Log any new server fields that were added
for (const serverField in defaultServer) {
if (!(serverField in userServer)) {
console.log(`Added missing server field: ${serverField}`);
}
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export const serverConfigSchema = z.object({
dashboardPassword: z.string().optional(),
dashboardPort: z.number().optional(),
websocketPort: z.number().optional(),
useRemoteWebSocket: z.boolean().optional(),
websocketHost: z.string().nullable().optional(),
}).optional();

export const globalConfigSchema = z.object({
Expand Down
27 changes: 12 additions & 15 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const PUBLIC_PATHS = ['/login', '/api/auth', '/api/health'];
const PUBLIC_PATHS = ['/login', '/api/auth', '/api/health', '/api/config'];

export async function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
Expand All @@ -13,27 +13,24 @@ export async function middleware(request: NextRequest) {

// Check for authentication cookie
const authCookie = request.cookies.get('auth-token');
const passwordSetCookie = request.cookies.get('password-required');

// Check if this looks like a valid auth token (basic validation)
// Real validation happens in the API routes
// If we have a valid auth token, allow access
if (authCookie && authCookie.value && authCookie.value.startsWith('YXV0aGVudGljYXRlZDo')) {
return NextResponse.next();
}

// Check if we're in development mode (no auth required)
if (process.env.NODE_ENV === 'development' && !authCookie) {
// In development, only redirect if there's evidence a password was set
// This is determined by the presence of a redirect parameter from a previous attempt
const hasRedirect = request.nextUrl.searchParams.get('redirect');
if (!hasRedirect) {
return NextResponse.next();
}
// Check if password is required (based on cookie set by config check)
if (passwordSetCookie && passwordSetCookie.value === 'true') {
// Password is required but no valid auth token, redirect to login
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', pathname);
return NextResponse.redirect(loginUrl);
}

// Redirect to login page
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', pathname);
return NextResponse.redirect(loginUrl);
// No password required or not yet determined, allow access
// The client will check and set the password-required cookie if needed
return NextResponse.next();
}

export const config = {
Expand Down