Files
infoscreen/AUTH_QUICKREF.md
RobbStarkAustria a7df3c2708 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.
2025-10-15 16:33:35 +00:00

5.6 KiB

Authentication Quick Reference

For Backend Developers

Protecting a Route

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

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

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

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

// 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

# 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:

python -c 'import secrets; print(secrets.token_hex(32))'

Testing Endpoints

# 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

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

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

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