Fixed re-auth setup and gitignore node modules

This commit is contained in:
JesseFPV
2026-01-22 12:21:07 +01:00
parent 821d3ba748
commit 3a848643c0
8 changed files with 586 additions and 494 deletions
+3
View File
@@ -82,6 +82,9 @@ function SetupRoute({ children }: { children: React.ReactNode }) {
return <div className="min-h-screen flex items-center justify-center">Loading...</div>;
}
// If auth is already enabled, redirect to login
// Otherwise, allow access to setup page (even if setup was completed before)
// This allows users to enable auth later if they skipped it during initial setup
if (authEnabled) {
return <Navigate to="/login" replace />;
}
+24 -5
View File
@@ -1,10 +1,11 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
import { api, getAuthToken, setAuthToken } from '../api/client';
import type { UserResponse } from '../api/client';
interface AuthContextType {
user: UserResponse | null;
authEnabled: boolean;
requiresSetup: boolean;
loading: boolean;
login: (username: string, password: string) => Promise<void>;
logout: () => void;
@@ -17,12 +18,15 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<UserResponse | null>(null);
const [authEnabled, setAuthEnabled] = useState(false);
const [requiresSetup, setRequiresSetup] = useState(false);
const [loading, setLoading] = useState(true);
const hasRedirectedRef = useRef(false);
const checkAuthStatus = async () => {
try {
const status = await api.getAuthStatus();
setAuthEnabled(status.auth_enabled);
setRequiresSetup(status.requires_setup);
if (status.auth_enabled) {
const token = getAuthToken();
@@ -41,10 +45,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
} else {
// Auth not enabled, allow access
setUser(null);
// Check if setup is needed
if (status.requires_setup && window.location.pathname !== '/setup') {
window.location.href = '/setup';
}
}
} catch (error) {
console.error('Failed to check auth status:', error);
@@ -56,9 +56,27 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
};
useEffect(() => {
// Check auth status on mount
checkAuthStatus();
}, []);
// Separate effect to handle redirect only when setup is required
useEffect(() => {
// Only redirect if setup is truly required (first time setup)
// Don't redirect if user manually navigated to /setup or is on camera page
if (!loading && requiresSetup && !authEnabled) {
const currentPath = window.location.pathname;
// Only redirect if not already on setup page or camera page, and haven't redirected yet
if (currentPath !== '/setup' && !currentPath.startsWith('/camera/') && !hasRedirectedRef.current) {
hasRedirectedRef.current = true;
window.location.href = '/setup';
}
} else if (!requiresSetup) {
// Reset redirect flag when setup is no longer required
hasRedirectedRef.current = false;
}
}, [loading, requiresSetup, authEnabled]);
const login = async (username: string, password: string) => {
const response = await api.login({ username, password });
setAuthToken(response.access_token);
@@ -95,6 +113,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
value={{
user,
authEnabled,
requiresSetup,
loading,
login,
logout,
+5 -1
View File
@@ -2996,7 +2996,11 @@ export function SettingsPage() {
</ul>
<Button
onClick={() => navigate('/setup')}
type="button"
onClick={(e) => {
e.preventDefault();
navigate('/setup');
}}
className="w-full"
>
<Lock className="w-4 h-4" />
+64 -37
View File
@@ -4,11 +4,14 @@ import { useMutation } from '@tanstack/react-query';
import { api } from '../api/client';
import { useToast } from '../contexts/ToastContext';
import { useTheme } from '../contexts/ThemeContext';
import { useAuth } from '../contexts/AuthContext';
import { Info } from 'lucide-react';
export function SetupPage() {
const navigate = useNavigate();
const { showToast } = useToast();
const { mode } = useTheme();
const { refreshAuth } = useAuth();
const [authEnabled, setAuthEnabled] = useState(false);
const [adminUsername, setAdminUsername] = useState('');
const [adminPassword, setAdminPassword] = useState('');
@@ -21,10 +24,18 @@ export function SetupPage() {
admin_username: authEnabled ? adminUsername : undefined,
admin_password: authEnabled ? adminPassword : undefined,
}),
onSuccess: (data) => {
if (data.auth_enabled && data.admin_created) {
showToast('Authentication enabled and admin user created');
navigate('/login');
onSuccess: async (data) => {
// Refresh auth status after setup
await refreshAuth();
if (data.auth_enabled) {
if (data.admin_created) {
showToast('Authentication enabled and admin user created');
navigate('/login');
} else {
showToast('Authentication enabled using existing admin users');
navigate('/login');
}
} else {
showToast('Setup completed');
navigate('/');
@@ -39,17 +50,21 @@ export function SetupPage() {
e.preventDefault();
if (authEnabled) {
if (!adminUsername || !adminPassword) {
showToast('Please enter admin username and password', 'error');
return;
}
if (adminPassword !== confirmPassword) {
showToast('Passwords do not match', 'error');
return;
}
if (adminPassword.length < 6) {
showToast('Password must be at least 6 characters', 'error');
return;
// Only validate if credentials are provided
// If no credentials provided, backend will use existing admin users if they exist
if (adminUsername || adminPassword) {
if (!adminUsername || !adminPassword) {
showToast('Please enter both admin username and password, or leave both empty to use existing admin users', 'error');
return;
}
if (adminPassword !== confirmPassword) {
showToast('Passwords do not match', 'error');
return;
}
if (adminPassword.length < 6) {
showToast('Password must be at least 6 characters', 'error');
return;
}
}
}
@@ -92,55 +107,67 @@ export function SetupPage() {
{authEnabled && (
<div className="space-y-4 mt-4">
<div className="p-3 bg-bambu-dark-secondary/50 border border-bambu-dark-tertiary rounded-lg">
<div className="flex items-start gap-2">
<Info className="w-4 h-4 text-bambu-green mt-0.5 flex-shrink-0" />
<div className="text-sm text-bambu-gray">
<p className="text-white font-medium mb-1">Admin Account</p>
<p>
If admin users already exist, authentication will be enabled using the existing admin accounts.
Leave the fields below empty to use existing admins, or enter new credentials to create a new admin user.
</p>
</div>
</div>
</div>
<div>
<label htmlFor="admin-username" className="block text-sm font-medium text-white mb-2">
Admin Username
Admin Username <span className="text-bambu-gray text-xs">(optional if admin users exist)</span>
</label>
<input
id="admin-username"
type="text"
required
value={adminUsername}
onChange={(e) => setAdminUsername(e.target.value)}
className="block w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
placeholder="Enter admin username"
placeholder="Enter admin username (optional)"
autoComplete="username"
/>
</div>
<div>
<label htmlFor="admin-password" className="block text-sm font-medium text-white mb-2">
Admin Password
Admin Password <span className="text-bambu-gray text-xs">(optional if admin users exist)</span>
</label>
<input
id="admin-password"
type="password"
required
value={adminPassword}
onChange={(e) => setAdminPassword(e.target.value)}
className="block w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
placeholder="Enter admin password"
placeholder="Enter admin password (optional)"
minLength={6}
autoComplete="new-password"
/>
</div>
<div>
<label htmlFor="confirm-password" className="block text-sm font-medium text-white mb-2">
Confirm Password
</label>
<input
id="confirm-password"
type="password"
required
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="block w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
placeholder="Confirm admin password"
minLength={6}
autoComplete="new-password"
/>
</div>
{adminPassword && (
<div>
<label htmlFor="confirm-password" className="block text-sm font-medium text-white mb-2">
Confirm Password
</label>
<input
id="confirm-password"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="block w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
placeholder="Confirm admin password"
minLength={6}
autoComplete="new-password"
/>
</div>
)}
</div>
)}
</div>