mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
Fixed re-auth setup and gitignore node modules
This commit is contained in:
@@ -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 />;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user