**`tutorials/03-server-modules-security.md`** ````markdown # 03 — Server Modules & Security ## Roles & Helper ```python import anvil.server from anvil import users, secrets ROLES = {"viewer","editor","admin"} def require_role(*roles): u = users.get_user() if not u: raise PermissionError("Login required") if not set(roles).intersection(set(u.get('roles', []))): raise PermissionError("Insufficient role") ```` ## Validated Callable ```python @anvil.server.callable def secure_greet(name): name = (name or "").strip() if not (1 <= len(name) <= 64): raise ValueError("Bad name") return f"Top-secret hello, {name}" ``` ## Secrets + Fake External ```python API_KEY = secrets.get_secret('DEMO_API_KEY') # set in Anvil ``` ## Simple Rate Limit (per user) Use a module-level dict `{user_id: [timestamps...]}`; trim to last 60s, allow ≤5. ## Client UX * Try/catch PermissionError → Notification("You need editor role"). * Disable button while awaiting server. ```` **`tutorials/04-uplink-file-hash.md`** ```markdown # 04 — Uplink: Local File Hash ## Uplink Script (local) ```python import anvil.server, hashlib @anvil.server.callable("hash_file") def hash_file(buffer): h = hashlib.sha256(buffer.get_bytes()) return h.hexdigest() anvil.server.connect("YOUR-UPLINK-KEY") anvil.server.wait_forever() ```` ## Client * FileLoader → `file_loader_1_change`: call `hash_file` with the Media object. * Show result; if timeout/conn error → “Uplink offline”. ```` **`tutorials/05-auth-roles-ui.md`** ```markdown # 05 — Auth: Users + Roles + UI Gating ## Setup Enable Users service. Add roles to users (e.g., `editor`, `admin`). ## Server Use `require_role` from Tutorial 03 to gate `dangerous_action`. ## Client - Add Login/Logout buttons. - Show current user + roles. - Hide/show editor-only actions via `component.visible`. ## Demo Flow - Viewer logs in → can’t see editor actions. - Editor logs in → sees & runs gated action. ````