Skip to content

Commit 6265ffe

Browse files
feat: Implement comprehensive authentication system (#99)
* feat: implement JWT-based authentication system - Add bcrypt password hashing and JWT token generation - Create blocking auth modals for login and setup - Add authentication management to General Settings - Implement API routes for login, verify, setup, and credential management - Add AuthProvider and AuthGuard components - Support first-time setup and persistent authentication - Store credentials securely in .env file * feat: add option to skip enabling auth during setup - Add toggle in SetupModal to choose whether to enable authentication immediately - Users can set up credentials but keep authentication disabled initially - Authentication can be enabled/disabled later through General Settings - Maintains flexibility for users who want to configure auth gradually * fix: allow proceeding without password when auth is disabled - Make password fields optional when authentication is disabled in setup - Update button validation to only require password when auth is enabled - Modify API to handle optional password parameter - Update hasCredentials logic to work with username-only setup - Users can now complete setup with just username when auth is disabled - Password can be added later when enabling authentication * feat: don't store credentials when authentication is disabled - When auth is disabled, no username or password is stored - Setup modal only requires credentials when authentication is enabled - Disabling authentication clears all stored credentials - Users can skip authentication entirely without storing any data - Clean separation between enabled/disabled authentication states * feat: add setup completed flag to prevent modal on every load - Add AUTH_SETUP_COMPLETED flag to track when user has completed setup - Setup modal only appears when setupCompleted is false - Both enabled and disabled auth setups mark setup as completed - Clean .env file when authentication is disabled (no empty credential lines) - Prevents setup modal from appearing on every page load after user decision * fix: add missing Authentication tab button in settings modal - Authentication tab button was missing from the tabs navigation - Users couldn't access authentication settings - Added Authentication tab button with proper styling and click handler - Authentication settings are now accessible through the settings modal * fix: properly load and display authentication settings - Add setupCompleted state variable to track setup status - Update loadAuthCredentials to include setupCompleted field - Fix authentication status display logic to show correct state - Show proper status when auth is disabled but setup is completed - Enable toggle only when setup is completed (not just when credentials exist) - Settings now correctly reflect the actual authentication state * fix: handle empty FILTERS environment variable - Add check for empty or invalid FILTERS JSON before parsing - Prevents 'Unexpected end of JSON input' error when FILTERS is empty - Return null filters instead of throwing parse error - Clean up empty FILTERS line from .env file - Fixes console error when loading settings modal * fix: load authentication credentials when settings modal opens - Add loadAuthCredentials() call to useEffect when modal opens - Authentication settings were not loading because the function wasn't being called - Now properly loads auth configuration when settings modal is opened - Settings will display the correct authentication status and state * fix: prevent multiple JWT secret generation with caching - Add JWT secret caching to prevent race conditions - Multiple API calls were generating duplicate JWT secrets - Now caches secret after first generation/read - Clean up duplicate JWT_SECRET lines from .env file - Prevents .env file from being cluttered with multiple secrets * feat: auto-login user after setup with authentication enabled - When user sets up authentication with credentials, automatically log them in - Prevents need to manually log in after setup completion - Setup modal now calls login API after successful setup when auth is enabled - AuthGuard no longer reloads page after setup, just refreshes config - Seamless user experience from setup to authenticated state * fix: resolve console errors and improve auth flow - Fix 401 Unauthorized error by checking setup status before auth verification - AuthProvider now checks if setup is completed before attempting to verify auth - Prevents unnecessary auth verification calls when no credentials exist - Add webpack polling configuration to fix WebSocket HMR issues - Improves development experience when accessing from different IPs - Eliminates console errors during initial setup flow * fix: resolve build errors and linting issues - Fix TypeScript ESLint error: use optional chain expression in auth.ts - Fix React Hook warning: add missing 'isRunning' dependency to useEffect in Terminal.tsx - Build now compiles successfully without any errors or warnings - All linting rules are now satisfied
1 parent 608a7ac commit 6265ffe

File tree

18 files changed

+1498
-6
lines changed

18 files changed

+1498
-6
lines changed

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,8 @@ WEBSOCKET_PORT="3001"
2121
GITHUB_TOKEN=
2222
SAVE_FILTER=false
2323
FILTERS=
24+
AUTH_USERNAME=
25+
AUTH_PASSWORD_HASH=
26+
AUTH_ENABLED=false
27+
AUTH_SETUP_COMPLETED=false
28+
JWT_SECRET=

next.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ const config = {
4242
'http://172.31.*',
4343
'http://192.168.*',
4444
],
45+
46+
webpack: (config, { dev, isServer }) => {
47+
if (dev && !isServer) {
48+
config.watchOptions = {
49+
poll: 1000,
50+
aggregateTimeout: 300,
51+
};
52+
}
53+
return config;
54+
},
4555
};
4656

4757
export default config;

package-lock.json

Lines changed: 150 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@
3333
"@xterm/addon-fit": "^0.10.0",
3434
"@xterm/addon-web-links": "^0.11.0",
3535
"@xterm/xterm": "^5.5.0",
36+
"bcryptjs": "^3.0.2",
3637
"better-sqlite3": "^12.4.1",
3738
"class-variance-authority": "^0.7.1",
3839
"clsx": "^2.1.1",
40+
"jsonwebtoken": "^9.0.2",
3941
"lucide-react": "^0.545.0",
4042
"next": "^15.5.3",
4143
"node-pty": "^1.0.0",
@@ -56,7 +58,9 @@
5658
"@testing-library/jest-dom": "^6.9.1",
5759
"@testing-library/react": "^16.3.0",
5860
"@testing-library/user-event": "^14.6.1",
61+
"@types/bcryptjs": "^2.4.6",
5962
"@types/better-sqlite3": "^7.6.8",
63+
"@types/jsonwebtoken": "^9.0.10",
6064
"@types/node": "^24.7.1",
6165
"@types/react": "^19.0.0",
6266
"@types/react-dom": "^19.0.0",

src/app/_components/AuthGuard.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
'use client';
2+
3+
import { useState, useEffect, type ReactNode } from 'react';
4+
import { useAuth } from './AuthProvider';
5+
import { AuthModal } from './AuthModal';
6+
import { SetupModal } from './SetupModal';
7+
8+
interface AuthGuardProps {
9+
children: ReactNode;
10+
}
11+
12+
interface AuthConfig {
13+
username: string | null;
14+
enabled: boolean;
15+
hasCredentials: boolean;
16+
setupCompleted: boolean;
17+
}
18+
19+
export function AuthGuard({ children }: AuthGuardProps) {
20+
const { isAuthenticated, isLoading } = useAuth();
21+
const [authConfig, setAuthConfig] = useState<AuthConfig | null>(null);
22+
const [configLoading, setConfigLoading] = useState(true);
23+
const [setupCompleted, setSetupCompleted] = useState(false);
24+
25+
const handleSetupComplete = async () => {
26+
setSetupCompleted(true);
27+
// Refresh auth config without reloading the page
28+
await fetchAuthConfig();
29+
};
30+
31+
const fetchAuthConfig = async () => {
32+
try {
33+
const response = await fetch('/api/settings/auth-credentials');
34+
if (response.ok) {
35+
const config = await response.json() as AuthConfig;
36+
setAuthConfig(config);
37+
}
38+
} catch (error) {
39+
console.error('Error fetching auth config:', error);
40+
} finally {
41+
setConfigLoading(false);
42+
}
43+
};
44+
45+
useEffect(() => {
46+
void fetchAuthConfig();
47+
}, []);
48+
49+
// Show loading while checking auth status
50+
if (isLoading || configLoading) {
51+
return (
52+
<div className="min-h-screen bg-background flex items-center justify-center">
53+
<div className="text-center">
54+
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mb-4"></div>
55+
<p className="text-muted-foreground">Loading...</p>
56+
</div>
57+
</div>
58+
);
59+
}
60+
61+
// Show setup modal if setup has not been completed yet
62+
if (authConfig && !authConfig.setupCompleted && !setupCompleted) {
63+
return <SetupModal isOpen={true} onComplete={handleSetupComplete} />;
64+
}
65+
66+
// Show auth modal if auth is enabled but user is not authenticated
67+
if (authConfig && authConfig.enabled && !isAuthenticated) {
68+
return <AuthModal isOpen={true} />;
69+
}
70+
71+
// Render children if authenticated or auth is disabled
72+
return <>{children}</>;
73+
}

0 commit comments

Comments
 (0)