18 KiB
18 KiB
Luna Recipes — Auth v2 Feature-Spezifikation
Übersicht
Implementierung eines vollständigen Authentifizierungssystems für die Luna Rezept-App mit Multi-User-Support und Haushaltsfunktionalität. Das System ermöglicht es mehreren Nutzern, die App zu verwenden, dabei ihre Daten zu trennen und optional einen gemeinsamen Haushalt zu teilen.
1. Datenmodell
1.1 Neue Tabellen
users
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
display_name TEXT NOT NULL,
avatar_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE UNIQUE INDEX idx_users_email ON users(email);
households
CREATE TABLE households (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
invite_code TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE UNIQUE INDEX idx_households_invite_code ON households(invite_code);
household_members
CREATE TABLE household_members (
household_id UUID REFERENCES households(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
role TEXT NOT NULL CHECK (role IN ('owner', 'member')),
joined_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (household_id, user_id)
);
user_favorites (Neue Tabelle)
CREATE TABLE user_favorites (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
recipe_id UUID REFERENCES recipes(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, recipe_id)
);
1.2 Erweiterte bestehende Tabellen
shopping_items
-- Erweitern um User- und Haushaltszuordnung
ALTER TABLE shopping_items ADD COLUMN user_id UUID REFERENCES users(id);
ALTER TABLE shopping_items ADD COLUMN household_id UUID REFERENCES households(id);
-- Migration: NULL user_id wird beim ersten Login zugewiesen
notes
-- Erweitern um User-Zuordnung
ALTER TABLE notes ADD COLUMN user_id UUID REFERENCES users(id);
-- Migration: Bestehende Notizen werden dem ersten registrierten User zugewiesen
recipes
-- Recipes bleiben global, aber mit Ersteller-Info
ALTER TABLE recipes ADD COLUMN created_by UUID REFERENCES users(id);
-- Migration: Bestehende Rezepte werden dem ersten registrierten User zugewiesen
1.3 Daten-Ownership
- Recipes: Global sichtbar,
created_byzur Information - Shopping Items: Pro User ODER pro Haushalt (wenn
household_idgesetzt) - Favorites: Pro User (user_favorites Tabelle)
- Notes: Pro User
- Households: Gemeinsam für alle Mitglieder
2. API Endpoints
2.1 Authentication
POST /api/auth/register
// Request
{
"email": "luna@example.com",
"password": "secure123!",
"display_name": "Luna"
}
// Response (201 Created)
{
"success": true,
"user": {
"id": "uuid-here",
"email": "luna@example.com",
"display_name": "Luna",
"avatar_url": null,
"created_at": "2026-02-18T15:30:00Z"
},
"access_token": "jwt-token-here"
}
// Error (400 Bad Request)
{
"success": false,
"error": "EMAIL_EXISTS",
"message": "Ein Account mit dieser E-Mail existiert bereits"
}
POST /api/auth/login
// Request
{
"email": "luna@example.com",
"password": "secure123!"
}
// Response (200 OK)
{
"success": true,
"user": {
"id": "uuid-here",
"email": "luna@example.com",
"display_name": "Luna",
"avatar_url": null
},
"access_token": "jwt-token-here"
}
// Error (401 Unauthorized)
{
"success": false,
"error": "INVALID_CREDENTIALS",
"message": "E-Mail oder Passwort ungültig"
}
POST /api/auth/logout
// Response (200 OK)
{
"success": true,
"message": "Erfolgreich abgemeldet"
}
GET /api/auth/me
// Response (200 OK)
{
"success": true,
"user": {
"id": "uuid-here",
"email": "luna@example.com",
"display_name": "Luna",
"avatar_url": "https://example.com/avatar.jpg",
"household": {
"id": "household-uuid",
"name": "Luna & Marc",
"role": "owner"
}
}
}
PUT /api/auth/me
// Request (Profil bearbeiten)
{
"display_name": "Luna Schmidt",
"avatar_url": "https://example.com/new-avatar.jpg"
}
// Response (200 OK)
{
"success": true,
"user": {
"id": "uuid-here",
"email": "luna@example.com",
"display_name": "Luna Schmidt",
"avatar_url": "https://example.com/new-avatar.jpg"
}
}
PUT /api/auth/me/password
// Request
{
"current_password": "old123!",
"new_password": "new456!"
}
// Response (200 OK)
{
"success": true,
"message": "Passwort erfolgreich geändert"
}
// Error (400 Bad Request)
{
"success": false,
"error": "INVALID_CURRENT_PASSWORD",
"message": "Aktuelles Passwort ist falsch"
}
2.2 Households
POST /api/households
// Request (Haushalt erstellen)
{
"name": "Luna & Marc"
}
// Response (201 Created)
{
"success": true,
"household": {
"id": "household-uuid",
"name": "Luna & Marc",
"invite_code": "COOK2024",
"created_at": "2026-02-18T15:30:00Z",
"members": [
{
"user_id": "user-uuid",
"display_name": "Luna",
"role": "owner",
"joined_at": "2026-02-18T15:30:00Z"
}
]
}
}
POST /api/households/:id/invite
// Response (200 OK) - Neuen Einladungscode generieren
{
"success": true,
"invite_code": "COOK2025",
"expires_at": "2026-02-25T15:30:00Z"
}
POST /api/households/join
// Request
{
"invite_code": "COOK2024"
}
// Response (200 OK)
{
"success": true,
"household": {
"id": "household-uuid",
"name": "Luna & Marc",
"role": "member"
}
}
// Error (400 Bad Request)
{
"success": false,
"error": "INVALID_INVITE_CODE",
"message": "Einladungscode ungültig oder abgelaufen"
}
GET /api/households/mine
// Response (200 OK)
{
"success": true,
"household": {
"id": "household-uuid",
"name": "Luna & Marc",
"invite_code": "COOK2024",
"role": "owner",
"members": [
{
"user_id": "user1-uuid",
"display_name": "Luna",
"role": "owner",
"joined_at": "2026-02-18T15:30:00Z"
},
{
"user_id": "user2-uuid",
"display_name": "Marc",
"role": "member",
"joined_at": "2026-02-19T10:15:00Z"
}
]
}
}
3. Frontend Pages
3.1 Authentication Pages
/login
- Design: Vollbild-Login mit Luna Recipes Logo
- Felder: E-Mail, Passwort, "Angemeldet bleiben" Checkbox
- Actions: Login, "Registrieren" Link, "Passwort vergessen" Link (v2.1)
- Mobile: Touch-optimierte Input-Felder, große Login-Button
- Validation: Client-side Validation mit sofortigem Feedback
- States: Loading State beim Login-Prozess
/register
- Design: Gleiche Basis wie Login-Page
- Felder: E-Mail, Passwort, Passwort wiederholen, Display Name
- Validation:
- E-Mail Format-Check
- Passwort-Stärke Indikator
- Passwort-Match Validation
- Display Name min. 2 Zeichen
- Actions: Registrieren, "Schon Account?" Login-Link
- Flow: Nach erfolgreicher Registrierung → automatisch eingeloggt → Dashboard
3.2 Profile Pages
/profile
- Layout: Tabbed Interface (Profil, Haushalt)
- Profil Tab:
- Avatar (Upload oder URL)
- Display Name (inline editierbar)
- E-Mail (nicht editierbar, mit "Ändern" Link für v2.1)
- "Passwort ändern" Button
- Mobile: Große Avatar-Anzeige, Touch-freundliche Edit-Buttons
/profile/edit
- Modal oder Fullscreen (Mobile): Profil bearbeiten
- Felder: Display Name, Avatar URL/Upload
- Actions: Speichern, Abbrechen
- Validation: Display Name required
/profile/password
- Layout: Focused Passwort-Ändern Page
- Felder: Aktuelles Passwort, Neues Passwort, Bestätigung
- Security:
- Passwort-Stärke Anzeige
- "Passwort anzeigen" Toggle
- Session-Refresh nach Änderung
- UX: Erfolgs-Toast + Redirect nach Speichern
/profile/household
- States:
- Kein Haushalt: "Haushalt erstellen" oder "Haushalt beitreten"
- Haushalt Owner: Mitglieder-Liste, Einladungscode verwalten
- Haushalt Member: Mitglieder-Liste, "Haushalt verlassen"
- Features:
- Einladungscode kopieren/teilen
- QR-Code für Einladung (Mobile-optimiert)
- Mitglieder-Management (nur Owner)
4. Authentication Flow
4.1 Token-Strategy
JWT Access Token
- Laufzeit: 15 Minuten
- Storage: Memory (React State + Context)
- Payload:
{ "sub": "user-uuid", "email": "luna@example.com", "display_name": "Luna", "household_id": "household-uuid", "iat": 1708272600, "exp": 1708273500 }
Refresh Token
- Laufzeit: 30 Tage
- Storage: httpOnly Cookie, Secure, SameSite=Strict
- Rotation: Neuer Refresh Token bei jedem Access Token Refresh
- Cookie Name:
luna_refresh_token
4.2 Auto-Refresh Flow
// Auth Context implementiert Auto-Refresh
const authContext = {
// Access Token im Memory
accessToken: string | null,
// User Info aus Token dekodiert
user: User | null,
// Auto-refresh 2 Minuten vor Ablauf
refreshToken: () => Promise<void>,
logout: () => void
}
// Axios Interceptor für Auto-Refresh
axios.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
await authContext.refreshToken()
// Retry original request
return axios.request(error.config)
}
return Promise.reject(error)
}
)
4.3 Route Protection
// Protected Route Component
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth()
if (loading) return <LoadingSpinner />
if (!user) return <Navigate to="/login" replace />
return <>{children}</>
}
// App Router
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
</Routes>
4.4 Login Flow States
- Unauthenticated: Redirect zu
/login - Login Success:
- Set Access Token in Memory
- Set Refresh Token in Cookie
- Redirect zu ursprünglicher Route oder
/
- Token Expired: Auto-refresh, bei Fehler → Logout
- Logout: Clear Token + Cookie, Redirect zu
/login
5. Haushalt-Feature
5.1 Haushalt erstellen
- User klickt "Haushalt erstellen" in
/profile/household - Modal: Haushalt-Name eingeben
- Backend erstellt Haushalt + generiert Einladungscode
- User wird automatisch als Owner hinzugefügt
- Einladungscode wird angezeigt zum Teilen
5.2 Haushalt beitreten
- User erhält Einladungscode (per Link, QR-Code oder Text)
- User klickt "Haushalt beitreten" und gibt Code ein
- Backend validiert Code und fügt User als Member hinzu
- Einkaufsliste wird automatisch geteilt
5.3 Gemeinsame Einkaufsliste
Datenlogik
- Shopping Items mit
household_idsind für alle Haushaltsmitglieder sichtbar - Shopping Items mit nur
user_idsind privat - Standard: Neue Items werden Haushalt zugeordnet (wenn User in Haushalt ist)
- Toggle: "Privat" Checkbox beim Hinzufügen
UI/UX
- Indicator: Haushalt-Items haben kleines Haushalt-Icon
- Filter: "Alle", "Haushalt", "Privat" Tabs in Shopping Liste
- Mobile: Swipe-Actions: "Als privat markieren" / "Mit Haushalt teilen"
5.4 Persönliche Favoriten
- Favoriten sind immer pro User (
user_favoritesTabelle) - Keine Sharing-Option für Favoriten in v2
- Favoriten-Liste zeigt nur eigene Favoriten
6. Migration Strategy
6.1 Backwards Compatibility
- Anonymous Access: Rezepte bleiben öffentlich zugänglich ohne Login
- URLs: Alle bestehenden Recipe-URLs funktionieren weiterhin
- Features: Basis-Funktionen (Rezepte suchen, ansehen) ohne Auth
6.2 Daten-Migration
Phase 1: Schema Updates
-- Neue Tabellen erstellen (siehe Datenmodell)
-- Bestehende Tabellen erweitern (users_id Spalten als optional)
-- Default User erstellen für Migration
INSERT INTO users (id, email, display_name, password_hash)
VALUES (
'migration-user-uuid',
'migration@luna-recipes.local',
'Luna (Migration)',
'no-login'
);
Phase 2: Daten zuweisen
-- Bestehende Rezepte dem Migration User zuweisen
UPDATE recipes
SET created_by = 'migration-user-uuid'
WHERE created_by IS NULL;
-- Bestehende Notes dem Migration User zuweisen
UPDATE notes
SET user_id = 'migration-user-uuid'
WHERE user_id IS NULL;
-- Shopping Items vorerst ohne user_id lassen
-- Werden beim ersten User-Login zugewiesen
Phase 3: Erste echte User
- Erste User die sich registrieren bekommen alle bestehenden Daten zugewiesen
- Migration User wird nach erstem echten User gelöscht
- Shopping Items ohne user_id werden beim Login zugewiesen
6.3 Rollback Plan
- Auth-Features sind additiv, keine Breaking Changes
- Bei Problemen: Auth-Routes deaktivieren, App läuft weiter ohne Auth
- Datenbank-Rollback: user_id Spalten auf NULL setzen
7. Sicherheit
7.1 Passwort-Sicherheit
// bcrypt mit 12 Rounds (Backend)
const passwordHash = await bcrypt.hash(password, 12)
// Passwort-Validierung (Frontend + Backend)
const passwordRules = {
minLength: 8,
requireUppercase: false, // UX-freundlich für v2
requireNumbers: false,
requireSpecialChars: false
}
7.2 JWT Security
// JWT Signing (Backend)
const accessToken = jwt.sign(
{
sub: user.id,
email: user.email,
display_name: user.display_name,
household_id: user.household_id
},
process.env.JWT_SECRET,
{ expiresIn: '15m' }
)
// httpOnly Cookie Config
res.cookie('luna_refresh_token', refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days
})
7.3 Rate Limiting
// Login Rate Limiting
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts per IP
message: 'Zu viele Login-Versuche, bitte warten Sie 15 Minuten',
standardHeaders: true
})
// Registration Rate Limiting
const registerLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 3, // 3 registrations per IP per hour
message: 'Zu viele Registrierungen, bitte warten Sie'
})
7.4 Input Validation
// Zod Schema für API Validation (Backend)
const registerSchema = z.object({
email: z.string().email('Ungültige E-Mail-Adresse'),
password: z.string().min(8, 'Passwort muss mindestens 8 Zeichen haben'),
display_name: z.string().min(2, 'Name muss mindestens 2 Zeichen haben')
.max(50, 'Name darf maximal 50 Zeichen haben')
})
// XSS Protection für User-generierte Inhalte
const sanitizedDisplayName = DOMPurify.sanitize(display_name)
8. Mobile-First UX Considerations
8.1 Touch Targets
- Minimum Size: 44px × 44px für alle Buttons
- Spacing: 8px minimum zwischen clickbaren Elementen
- Form Fields: 48px Höhe für bessere Touch-Ergonomie
8.2 Responsive Breakpoints
/* Mobile First */
.auth-form {
width: 100%;
padding: 1rem;
}
/* Tablet */
@media (min-width: 768px) {
.auth-form {
max-width: 400px;
margin: 0 auto;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
}
8.3 Keyboard & Input Handling
// Input Types für bessere Mobile Keyboards
<input
type="email"
inputMode="email"
autoComplete="username"
placeholder="luna@example.com"
/>
<input
type="password"
autoComplete="current-password"
placeholder="Passwort"
/>
// Auto-focus Management
<input ref={emailRef} autoFocus onSubmit={focusPassword} />
8.4 Loading States
- Skeleton Loading: Für User Profile, Haushalt-Listen
- Button States: Loading Spinner in Buttons während API Calls
- Toast Messages: Für Success/Error Feedback
- Optimistic Updates: Favoriten, Haushalt beitreten
8.5 Offline Considerations
- Service Worker: Caching für Auth-relevante Pages
- Network Awareness: Retry-Mechanismus für failed Requests
- Local Storage: Backup für kritische User Preferences
9. Testing Strategy
9.1 Unit Tests
- Auth Context / Hook Tests
- Password Validation Tests
- JWT Token Handling Tests
- API Response Validation Tests
9.2 Integration Tests
- Login/Register Flow End-to-End
- Token Refresh Flow
- Haushalt erstellen/beitreten Flow
- Migration Logic Tests
9.3 Security Tests
- SQL Injection Prevention
- XSS Prevention
- CSRF Protection Validation
- Rate Limiting Effectiveness
10. Performance
10.1 Bundle Size
- Code Splitting: Auth-Pages lazy loaded
- Tree Shaking: Nur genutzte Auth-Libraries
- JWT Library: Lightweight jwt-decode statt vollständiger Library
10.2 API Optimization
- Response Caching: User Profile Daten
- Debounced Requests: Profile Updates
- Batch Requests: Initial App Load (User + Household in einem Call)
10.3 Database Performance
-- Wichtige Indizes für Auth Queries
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_household_members_user_id ON household_members(user_id);
CREATE INDEX idx_shopping_items_user_household ON shopping_items(user_id, household_id);
CREATE INDEX idx_user_favorites_user_id ON user_favorites(user_id);
Implementierungs-Prioritäten
Phase 1 (MVP)
- ✅ Datenbank Schema + Migration
- ✅ Backend Auth Endpoints
- ✅ Frontend Login/Register Pages
- ✅ JWT Token Flow + Auto-Refresh
- ✅ Route Protection
Phase 2 (Multi-User)
- User Profile Management
- Shopping Items User-Zuordnung
- Favorites pro User
- Notes pro User
Phase 3 (Households)
- Household Creation + Joining
- Shared Shopping Lists
- Household Management UI
- Invite Code Generation
Phase 4 (Polish)
- Avatar Upload
- Advanced Security Features
- Performance Optimizations
- Mobile UX Improvements
Geschätzte Entwicklungszeit: 3-4 Wochen für komplette Implementierung