Skip to content

Conversation

@t0mdavid-m
Copy link
Member

@t0mdavid-m t0mdavid-m commented Jan 27, 2026

Summary

This PR adds admin-only functionality to save current workspaces as reusable demo templates. It includes password-protected access to prevent unauthorized demo creation, proper secret management, and safe file handling for symlinks.

Key Changes

  • New admin module (src/common/admin.py): Provides admin utilities including:

    • Admin password verification using constant-time comparison (HMAC) to prevent timing attacks
    • Demo workspace management (check existence, save workspaces)
    • Safe directory removal that properly handles symlinks
  • Secrets management:

    • Added .streamlit/secrets.toml to .gitignore to prevent accidental credential commits
    • Created .streamlit/secrets.toml.example as a template for configuration
  • UI integration (src/common/common.py):

    • Added "Save as Demo" expander section in workspace management
    • Password-protected dialog for admin authentication
    • Validation for demo names and existing demo detection with overwrite confirmation
    • User-friendly error messages for configuration and permission issues

Implementation Details

  • Uses hmac.compare_digest() for secure password comparison to mitigate timing attacks
  • Follows symlinks during copy operation to store actual file contents rather than symlink references
  • Validates demo names using existing is_safe_workspace_name() utility to prevent path traversal attacks
  • Gracefully handles edge cases: missing configs, permission errors, existing demos
  • Demo workspaces are stored in example-data/workspaces/ directory

https://claude.ai/code/session_01JzDsFmXBRSFdvGV4TgqXwk

Summary by CodeRabbit

  • New Features

    • Save the current workspace as a demo in online mode, with overwrite confirmation, admin password prompt, and clear success/error feedback.
  • Documentation

    • Added a template for configuring Streamlit secrets and an admin password guide.
  • Chores

    • Excluded local secrets (secrets.toml) from version control to prevent accidental commits.

✏️ Tip: You can customize this high-level summary in your review settings.

Adds a new "Save as Demo" option in the sidebar (online mode only) that
allows password-protected saving of the current workspace as a demo.
The admin password is stored in .streamlit/secrets.toml which is gitignored.

- Add src/common/admin.py with admin utilities (password verification,
  demo saving with symlink handling)
- Add .streamlit/secrets.toml.example as a template
- Modify sidebar to show Save as Demo expander in online mode
- Warn and require confirmation when overwriting existing demos

https://claude.ai/code/session_01JzDsFmXBRSFdvGV4TgqXwk
@coderabbitai
Copy link

coderabbitai bot commented Jan 27, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds admin authentication and demo workspace management: new admin utilities for password verification and demo saving (symlink-aware), a sidebar UI to "Save as Demo" with password flow, a secrets template, and .gitignore entry to exclude .streamlit/secrets.toml.

Changes

Cohort / File(s) Summary
Configuration & Secrets
\.gitignore, .streamlit/secrets.toml.example
Ignore .streamlit/secrets.toml; add secrets.toml example with an [admin] password template and comments.
Admin Module
src/common/admin.py
New admin utilities: is_admin_configured, verify_admin_password (constant-time compare), get_demo_target_dir, demo_exists, save_workspace_as_demo (validations, symlink-aware copy, error handling), and a helper to remove dirs with symlinks.
Common Module Integration
src/common/common.py
Imports admin functions and adds "Save as Demo" sidebar UI: demo name input, overwrite confirmation, admin password dialog, calls to verify_admin_password and save_workspace_as_demo, result messages and conditional rerun.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Streamlit UI
    participant Admin as Admin Module
    participant FS as File System

    User->>UI: Enter demo name & click "Save as Demo"
    UI->>Admin: is_admin_configured()
    Admin-->>UI: configured? 

    alt not configured
        UI->>User: Show error (admin not configured)
    else configured
        User->>UI: Enter admin password
        UI->>Admin: verify_admin_password(password)
        Admin-->>UI: verification result

        alt verified
            UI->>Admin: save_workspace_as_demo(workspace_path, demo_name)
            Admin->>FS: validate source & target, prepare dirs
            Admin->>FS: remove existing demo (handle symlinks)
            Admin->>FS: copytree (follow symlinks)
            FS-->>Admin: success / error
            Admin-->>UI: return success or error message
            UI->>User: Display result (and rerun on success)
        else rejected
            UI->>User: Show password incorrect
        end
    end
Loading

Possibly related PRs

Poem

🐰
I hopped through secrets, quiet and small,
Saved demos safe within my hall.
Passwords guarded, symlinks in tow,
Templates planted where they should grow.
Hooray—demo carrots ready to show!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding admin functionality to save workspaces as demo templates, which aligns with the primary objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/common/admin.py`:
- Line 13: The top-level import of is_safe_workspace_name in src/common/admin.py
creates a circular import with src/common/common.py; to fix, remove the
module-level import and either move is_safe_workspace_name into a new utility
module (e.g., src/common/utils.py) and import it from there, or defer the import
inside the save_workspace_as_demo function so the import happens at runtime;
update references in save_workspace_as_demo to use the deferred or relocated
symbol and ensure tests/imports still resolve.

In `@src/common/common.py`:
- Around line 664-693: The admin password stored under the streamlit session key
"admin-password-input" is never removed; update the confirm and cancel flows in
the block that checks st.session_state.get("show_admin_password_dialog") so the
key is deleted after use: when verify_admin_password(admin_password) succeeds
(before hiding the dialog and rerun) remove the "admin-password-input" entry
from st.session_state; when the password is invalid (the else branch under
verify_admin_password) delete the key before showing the error; and when the
Cancel button handler runs delete the key before hiding the dialog and calling
st.rerun(); reference the UI elements and functions in this block (st.text_input
key "admin-password-input", verify_admin_password, save_workspace_as_demo) to
locate where to add the cleanup.

Comment on lines +664 to +693
if st.session_state.get("show_admin_password_dialog", False):
admin_password = st.text_input(
"Admin password",
type="password",
key="admin-password-input",
help="Enter the admin password to save this workspace as a demo"
)

col1, col2 = st.columns(2)
with col1:
if st.button("Confirm", key="confirm-save-demo"):
if verify_admin_password(admin_password):
success, message = save_workspace_as_demo(
st.session_state.workspace,
demo_name_clean
)
if success:
st.success(message)
st.session_state["show_admin_password_dialog"] = False
time.sleep(1)
st.rerun()
else:
st.error(message)
else:
st.error("Invalid admin password.")

with col2:
if st.button("Cancel", key="cancel-save-demo"):
st.session_state["show_admin_password_dialog"] = False
st.rerun()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "admin-password-input" src/common/common.py

Repository: OpenMS/streamlit-template

Length of output: 128


🏁 Script executed:

sed -n '660,700p' src/common/common.py

Repository: OpenMS/streamlit-template

Length of output: 2180


🏁 Script executed:

rg -n "session_state.*pop|session_state.*del|session_state.*clear" src/

Repository: OpenMS/streamlit-template

Length of output: 148


🏁 Script executed:

sed -n '280,290p' src/view.py

Repository: OpenMS/streamlit-template

Length of output: 392


🏁 Script executed:

sed -n '664,695p' src/common/common.py | cat -n

Repository: OpenMS/streamlit-template

Length of output: 2000


Clear the admin password from session state after use.

The password input is stored in st.session_state and never cleared. The password persists in memory across all exit paths—success, invalid password attempt, and cancel—even after the dialog is hidden. Remove the key on success, cancel, and invalid password to avoid retaining secrets longer than needed.

🔧 Suggested cleanup
                         if st.button("Confirm", key="confirm-save-demo"):
                             if verify_admin_password(admin_password):
                                 success, message = save_workspace_as_demo(
                                     st.session_state.workspace,
                                     demo_name_clean
                                 )
                                 if success:
                                     st.success(message)
                                     st.session_state["show_admin_password_dialog"] = False
+                                    st.session_state.pop("admin-password-input", None)
                                     time.sleep(1)
                                     st.rerun()
                                 else:
                                     st.error(message)
                             else:
                                 st.error("Invalid admin password.")
+                                st.session_state.pop("admin-password-input", None)

                         with col2:
                             if st.button("Cancel", key="cancel-save-demo"):
                                 st.session_state["show_admin_password_dialog"] = False
+                                st.session_state.pop("admin-password-input", None)
                                 st.rerun()
🤖 Prompt for AI Agents
In `@src/common/common.py` around lines 664 - 693, The admin password stored under
the streamlit session key "admin-password-input" is never removed; update the
confirm and cancel flows in the block that checks
st.session_state.get("show_admin_password_dialog") so the key is deleted after
use: when verify_admin_password(admin_password) succeeds (before hiding the
dialog and rerun) remove the "admin-password-input" entry from st.session_state;
when the password is invalid (the else branch under verify_admin_password)
delete the key before showing the error; and when the Cancel button handler runs
delete the key before hiding the dialog and calling st.rerun(); reference the UI
elements and functions in this block (st.text_input key "admin-password-input",
verify_admin_password, save_workspace_as_demo) to locate where to add the
cleanup.

Defer the import of is_safe_workspace_name inside save_workspace_as_demo
to avoid circular dependency between admin.py and common.py.

https://claude.ai/code/session_01JzDsFmXBRSFdvGV4TgqXwk
@t0mdavid-m t0mdavid-m merged commit 8e2a34d into main Jan 28, 2026
4 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants