feat: consolidate auth into NocoDB, add password reset, health check
Deploy / deploy (push) Failing after 9s

- Migrate auth credentials from SQLite (auth.db) to NocoDB Users table
  with one-time migration function (auth.db → auth.db.bak)
- Add email-based password reset via Cloudron SMTP (nodemailer)
- Add GET /api/health endpoint for monitoring
- Add startup env var validation with clear error messages
- Strip sensitive fields (password_hash, reset_token) from all API responses
- Add ForgotPassword + ResetPassword pages with i18n (en/ar)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-04 11:47:27 +03:00
parent 42a5f17d0b
commit c31e6222d7
12 changed files with 670 additions and 58 deletions
+12 -1
View File
@@ -3,12 +3,14 @@ import { useState, useEffect, createContext, lazy, Suspense } from 'react'
import { AuthProvider, useAuth } from './contexts/AuthContext'
import { LanguageProvider } from './i18n/LanguageContext'
import { ToastProvider } from './components/ToastContainer'
import { ThemeProvider } from './contexts/ThemeContext'
import ErrorBoundary from './components/ErrorBoundary'
import Layout from './components/Layout'
import Tutorial from './components/Tutorial'
import Modal from './components/Modal'
import { api } from './utils/api'
import { useLanguage } from './i18n/LanguageContext'
import { useKeyboardShortcuts, DEFAULT_SHORTCUTS } from './hooks/useKeyboardShortcuts'
// Lazy-loaded page components
const Dashboard = lazy(() => import('./pages/Dashboard'))
@@ -32,6 +34,8 @@ const PublicReview = lazy(() => import('./pages/PublicReview'))
const Issues = lazy(() => import('./pages/Issues'))
const PublicIssueSubmit = lazy(() => import('./pages/PublicIssueSubmit'))
const PublicIssueTracker = lazy(() => import('./pages/PublicIssueTracker'))
const ForgotPassword = lazy(() => import('./pages/ForgotPassword'))
const ResetPassword = lazy(() => import('./pages/ResetPassword'))
const TEAM_ROLES = [
{ value: 'manager', label: 'Manager' },
@@ -62,6 +66,9 @@ function AppContent() {
const [profileForm, setProfileForm] = useState({ name: '', team_role: '', phone: '', brands: '' })
const [profileSaving, setProfileSaving] = useState(false)
// Keyboard shortcuts
useKeyboardShortcuts(DEFAULT_SHORTCUTS)
useEffect(() => {
if (user && !authLoading) {
loadInitialData()
@@ -277,6 +284,8 @@ function AppContent() {
<Suspense fallback={<div className="min-h-screen bg-surface-secondary flex items-center justify-center"><div className="animate-pulse text-text-tertiary">Loading...</div></div>}>
<Routes>
<Route path="/login" element={user ? <Navigate to="/" replace /> : <Login />} />
<Route path="/forgot-password" element={user ? <Navigate to="/" replace /> : <ForgotPassword />} />
<Route path="/reset-password" element={user ? <Navigate to="/" replace /> : <ResetPassword />} />
<Route path="/review/:token" element={<PublicReview />} />
<Route path="/submit-issue" element={<PublicIssueSubmit />} />
<Route path="/track/:token" element={<PublicIssueTracker />} />
@@ -320,7 +329,9 @@ function App() {
<LanguageProvider>
<AuthProvider>
<ToastProvider>
<AppContent />
<ThemeProvider>
<AppContent />
</ThemeProvider>
</ToastProvider>
</AuthProvider>
</LanguageProvider>