FluidFrameDev/tutorials/05-auth-roles-ui.md

168 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 05 — Auth: Users Service + Role-Gated UI
**Goal:**
- Require login for certain actions.
- Show/hide UI components based on user role.
- Gate sensitive server functions so only specific roles can run them.
---
## 1) What Youll Build
- A login/logout button set.
- UI elements that only appear for specific roles (`editor`, `admin`).
- A server callable (`dangerous_action`) that only certain roles can call.
- Friendly messages when a user tries to overstep their privileges.
---
## 2) Enable the Users Service
In your Anvil app:
1. **App → Settings → Users Service → Enable**.
2. Add a few test users in the Users table:
- Columns: `email`, `password`, `roles` (Text, Comma-separated, e.g., `editor`, `admin`).
3. Assign roles for each user. Example:
- `alice@example.com` → roles: `editor`
- `bob@example.com` → roles: `viewer`
- `root@example.com` → roles: `admin`
---
## 3) Server-Side Role Helper
Add to `ServerModule1` (or create `auth_helpers.py` if you prefer):
```python
import anvil.server
import anvil.users
def require_role(*roles):
"""
Ensure the logged-in user has at least one of the given roles.
"""
user = anvil.users.get_user()
if not user:
raise PermissionError("You must be logged in.")
user_roles = set((user.get('roles') or "").replace(" ", "").split(","))
if not set(roles).intersection(user_roles):
raise PermissionError(f"You need one of these roles: {', '.join(roles)}")
@anvil.server.callable
def dangerous_action():
"""
Only 'editor' or 'admin' can run this.
"""
require_role('editor', 'admin')
return "Sensitive operation completed successfully."
````
---
## 4) Client UI Setup
On `Form1`:
* Add two Buttons:
* `button_login` text: Login
* `button_logout` text: Logout
* Add a Label: `label_user_info` (for current user/roles).
* Add a Button: `button_danger` text: Run Dangerous Action (editor/admin only).
* Arrange them neatly in a ColumnPanel or RowPanel.
---
## 5) Client Code (Form1)
```python
from anvil import *
import anvil.server
import anvil.users
class Form1(Form1Template):
def __init__(self, **properties):
self.init_components(**properties)
self.refresh_ui()
def refresh_ui(self):
"""Refreshes UI based on login status and roles."""
user = anvil.users.get_user()
if user:
roles = (user.get('roles') or "").replace(" ", "")
self.label_user_info.text = f"Logged in as: {user['email']} ({roles})"
self.button_login.visible = False
self.button_logout.visible = True
# Show dangerous button only if editor/admin
role_list = roles.split(",")
self.button_danger.visible = any(r in ('editor', 'admin') for r in role_list)
else:
self.label_user_info.text = "Not logged in"
self.button_login.visible = True
self.button_logout.visible = False
self.button_danger.visible = False
def button_login_click(self, **event_args):
"""Log the user in."""
anvil.users.login_with_form()
self.refresh_ui()
def button_logout_click(self, **event_args):
"""Log the user out."""
anvil.users.logout()
self.refresh_ui()
def button_danger_click(self, **event_args):
"""Attempt to run the dangerous action."""
try:
res = anvil.server.call('dangerous_action')
Notification(res, style='success', timeout=3).show()
except PermissionError as e:
Notification(str(e), style='danger', timeout=3).show()
except Exception as e:
Notification(f"Error: {e}", style='danger').show()
```
---
## 6) Test Scenarios
1. **Not logged in** → No dangerous button visible; clicking login prompts login form.
2. **Logged in as viewer** → Sees dangerous button hidden.
3. **Logged in as editor/admin** → Sees dangerous button; clicking it runs `dangerous_action` and shows success message.
4. **Force test**: Temporarily show dangerous button for all roles, click it as a viewer, verify server rejects with `PermissionError`.
---
## 7) Bonus: Role Badge
* Add a Label next to `label_user_info` styled with a background color based on role.
* Example: `editor` → blue badge, `admin` → red badge.
---
## 8) Demo Asset
Record a short GIF showing:
1. Logging in as viewer → no dangerous button.
2. Logging in as editor → dangerous button appears and works.
3. Logging in as admin → same, but maybe with admin-only UI section.
Save as: `/assets/auth-roles-demo.gif`
Reference in this tutorial:
```markdown
![Auth Roles Demo](../assets/auth-roles-demo.gif)
```
---
## 9) Takeaways
* Roles allow fine-grained control without changing client code.
* Always check roles **server-side** — the client can be modified by anyone.
* Hide UI for UX; enforce on server for security.
---