feat(dashboard): header user dropdown (Syncfusion) + proper logout; docs: clarify architecture; build: add splitbuttons; bump alpha.10
Dashboard Add top-right user dropdown using Syncfusion DropDownButton: shows username + role; menu entries “Profil” and “Abmelden”. Replace custom dropdown logic with Syncfusion component; position at header’s right edge. Update /logout page to call backend logout and redirect to /login (reliable user switching). Build/Config Add @syncfusion/ej2-react-splitbuttons and @syncfusion/ej2-splitbuttons dependencies. Update Vite optimizeDeps.include to pre-bundle splitbuttons and avoid import-analysis errors. Docs README: Rework Architecture Overview with clearer data flow: Listener consumes MQTT (discovery/heartbeats) and updates API. Scheduler reads from API and publishes events via MQTT to clients. Clients send via MQTT and receive via MQTT. Worker receives commands directly from API and reports results back (no MQTT). Explicit note: MariaDB is accessed exclusively by the API Server; Dashboard never talks to DB directly. README: Add SplitButtons to “Syncfusion Components Used”; add troubleshooting steps for @syncfusion/ej2-react-splitbuttons import issues (optimizeDeps + volume reset). Copilot instructions: Document header user menu and splitbuttons technical notes (deps, optimizeDeps, dev-container node_modules volume). Program info Bump to 2025.1.0-alpha.10 with changelog: UI: Header user menu (DropDownButton with username/role; Profil/Abmelden). Frontend: Syncfusion SplitButtons integration + Vite pre-bundling config. Fix: Added README guidance for splitbuttons import errors. No breaking changes.
This commit is contained in:
264
AUTH_QUICKREF.md
Normal file
264
AUTH_QUICKREF.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Authentication Quick Reference
|
||||
|
||||
## For Backend Developers
|
||||
|
||||
### Protecting a Route
|
||||
|
||||
```python
|
||||
from flask import Blueprint
|
||||
from server.permissions import require_role, admin_or_higher, editor_or_higher
|
||||
|
||||
my_bp = Blueprint("myroute", __name__, url_prefix="/api/myroute")
|
||||
|
||||
# Specific role(s)
|
||||
@my_bp.route("/admin")
|
||||
@require_role('admin', 'superadmin')
|
||||
def admin_only():
|
||||
return {"message": "Admin only"}
|
||||
|
||||
# Convenience decorators
|
||||
@my_bp.route("/settings")
|
||||
@admin_or_higher
|
||||
def settings():
|
||||
return {"message": "Admin or superadmin"}
|
||||
|
||||
@my_bp.route("/create", methods=["POST"])
|
||||
@editor_or_higher
|
||||
def create():
|
||||
return {"message": "Editor, admin, or superadmin"}
|
||||
```
|
||||
|
||||
### Getting Current User in Route
|
||||
|
||||
```python
|
||||
from flask import session
|
||||
|
||||
@my_bp.route("/profile")
|
||||
@require_auth
|
||||
def profile():
|
||||
user_id = session.get('user_id')
|
||||
username = session.get('username')
|
||||
role = session.get('role')
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"username": username,
|
||||
"role": role
|
||||
}
|
||||
```
|
||||
|
||||
## For Frontend Developers
|
||||
|
||||
### Using the Auth Hook
|
||||
|
||||
```typescript
|
||||
import { useAuth } from './useAuth';
|
||||
|
||||
function MyComponent() {
|
||||
const { user, isAuthenticated, login, logout, loading } = useAuth();
|
||||
|
||||
if (loading) return <div>Loading...</div>;
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <button onClick={() => login('user', 'pass')}>Login</button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Welcome {user?.username}</p>
|
||||
<p>Role: {user?.role}</p>
|
||||
<button onClick={logout}>Logout</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Rendering
|
||||
|
||||
```typescript
|
||||
import { useCurrentUser } from './useAuth';
|
||||
import { isAdminOrHigher, isEditorOrHigher } from './apiAuth';
|
||||
|
||||
function Navigation() {
|
||||
const user = useCurrentUser();
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
|
||||
{/* Show for all authenticated users */}
|
||||
{user && <a href="/events">Events</a>}
|
||||
|
||||
{/* Show for editor+ */}
|
||||
{isEditorOrHigher(user) && (
|
||||
<a href="/events/new">Create Event</a>
|
||||
)}
|
||||
|
||||
{/* Show for admin+ */}
|
||||
{isAdminOrHigher(user) && (
|
||||
<a href="/admin">Admin Panel</a>
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Making Authenticated API Calls
|
||||
|
||||
```typescript
|
||||
// Always include credentials for session cookies
|
||||
const response = await fetch('/api/protected-route', {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// ... other options
|
||||
});
|
||||
```
|
||||
|
||||
## Role Hierarchy
|
||||
|
||||
```
|
||||
superadmin > admin > editor > user
|
||||
```
|
||||
|
||||
| Role | Can Do |
|
||||
|------|--------|
|
||||
| **user** | View events |
|
||||
| **editor** | user + CRUD events/media |
|
||||
| **admin** | editor + manage users/groups/settings |
|
||||
| **superadmin** | admin + manage superadmins + system config |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# Required for sessions
|
||||
FLASK_SECRET_KEY=your_secret_key_here
|
||||
|
||||
# Required for superadmin creation
|
||||
DEFAULT_SUPERADMIN_USERNAME=superadmin
|
||||
DEFAULT_SUPERADMIN_PASSWORD=your_password_here
|
||||
```
|
||||
|
||||
Generate a secret key:
|
||||
```bash
|
||||
python -c 'import secrets; print(secrets.token_hex(32))'
|
||||
```
|
||||
|
||||
## Testing Endpoints
|
||||
|
||||
```bash
|
||||
# Login
|
||||
curl -X POST http://localhost:8000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"superadmin","password":"your_password"}' \
|
||||
-c cookies.txt
|
||||
|
||||
# Check current user
|
||||
curl http://localhost:8000/api/auth/me -b cookies.txt
|
||||
|
||||
# Check auth status (lightweight)
|
||||
curl http://localhost:8000/api/auth/check -b cookies.txt
|
||||
|
||||
# Logout
|
||||
curl -X POST http://localhost:8000/api/auth/logout -b cookies.txt
|
||||
|
||||
# Test protected route
|
||||
curl http://localhost:8000/api/protected -b cookies.txt
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Backend: Optional Auth
|
||||
|
||||
```python
|
||||
from flask import session
|
||||
|
||||
@my_bp.route("/public-with-extras")
|
||||
def public_route():
|
||||
user_id = session.get('user_id')
|
||||
|
||||
if user_id:
|
||||
# Show extra content for authenticated users
|
||||
return {"data": "...", "extras": "..."}
|
||||
else:
|
||||
# Public content only
|
||||
return {"data": "..."}
|
||||
```
|
||||
|
||||
### Frontend: Redirect After Login
|
||||
|
||||
```typescript
|
||||
const { login } = useAuth();
|
||||
|
||||
const handleLogin = async (username: string, password: string) => {
|
||||
try {
|
||||
await login(username, password);
|
||||
window.location.href = '/dashboard';
|
||||
} catch (err) {
|
||||
console.error('Login failed:', err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Frontend: Protected Route Component
|
||||
|
||||
```typescript
|
||||
import { useAuth } from './useAuth';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
||||
const { isAuthenticated, loading } = useAuth();
|
||||
|
||||
if (loading) return <div>Loading...</div>;
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Navigate to="/login" />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
// Usage in routes:
|
||||
<Route path="/admin" element={
|
||||
<ProtectedRoute>
|
||||
<AdminPanel />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Authentication required" on /api/auth/me
|
||||
|
||||
✅ **Normal** - User is not logged in. This is expected behavior.
|
||||
|
||||
### Session not persisting across requests
|
||||
|
||||
- Check `credentials: 'include'` in fetch calls
|
||||
- Verify `FLASK_SECRET_KEY` is set
|
||||
- Check browser cookies are enabled
|
||||
|
||||
### 403 Forbidden on decorated route
|
||||
|
||||
- Verify user is logged in
|
||||
- Check user role matches required role
|
||||
- Inspect response for `required_roles` and `your_role`
|
||||
|
||||
## Files Reference
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `server/routes/auth.py` | Auth endpoints (login, logout, /me) |
|
||||
| `server/permissions.py` | Permission decorators |
|
||||
| `dashboard/src/apiAuth.ts` | Frontend API client |
|
||||
| `dashboard/src/useAuth.tsx` | React context/hooks |
|
||||
| `models/models.py` | User model and UserRole enum |
|
||||
|
||||
## Full Documentation
|
||||
|
||||
See `AUTH_SYSTEM.md` for complete documentation including:
|
||||
- Architecture details
|
||||
- Security considerations
|
||||
- API reference
|
||||
- Testing guide
|
||||
- Production checklist
|
||||
Reference in New Issue
Block a user