appmanager-server 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/.env.example +10 -1
- appmanager_server-0.2.1/CHANGELOG.md +31 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/MANIFEST.in +1 -0
- {appmanager_server-0.2.0/appmanager_server.egg-info → appmanager_server-0.2.1}/PKG-INFO +1 -1
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/__init__.py +1 -1
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/auth/routes.py +45 -12
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/config.py +15 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1/appmanager_server.egg-info}/PKG-INFO +1 -1
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager_server.egg-info/SOURCES.txt +1 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/pyproject.toml +1 -1
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_auth.py +47 -2
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_packaging_and_cli.py +2 -2
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/LICENSE +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/README.md +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/SECURITY.md +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/admin/__init__.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/admin/app_installer.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/admin/members.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/admin/registry.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/admin/routes.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/api/__init__.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/api/routes.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/app_config.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/auth/__init__.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/auth/oauth.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/auth/utils.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/bridge.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/cli.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/database.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/extension.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/extensions.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/health.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/hooks.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/middleware.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/models.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/py.typed +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/sdk.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/security.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/security_scanner.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/signals.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/css/app.css +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/fonts.css +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-0.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-1.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-10.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-11.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-12.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-13.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-14.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-15.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-16.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-17.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-18.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-19.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-2.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-20.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-21.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-22.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-23.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-3.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-4.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-5.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-6.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-7.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-8.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/jetbrainsmono-9.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-0.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-1.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-10.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-11.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-2.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-3.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-4.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-5.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-6.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-7.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-8.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/spacegrotesk-9.woff2 +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/htmx/htmx.min.js +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/_member_drawer.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/_members_table.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/app_detail.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/dashboard.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/flairs.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/permissions.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/roles.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/users.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/auth/login.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/auth/profile.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/base.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/errors/401.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/errors/403.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/errors/404.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/errors/500.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/index.html +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager_server.egg-info/dependency_links.txt +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager_server.egg-info/entry_points.txt +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager_server.egg-info/requires.txt +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager_server.egg-info/top_level.txt +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/requirements.txt +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/setup.cfg +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_api.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_app_settings.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_app_settings_config.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_cli_dev_and_templates.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_cli_health.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_cli_scaffolding.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_error_pages.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_extension_apps.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_extension_pattern.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_hooks.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_installer.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_invalid_install_failures.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_manifest_sdk.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_middleware.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_role_management.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_sdk.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_security_hardening.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_security_precheck_flow.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_security_scanner.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_signals.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_user_elevation_and_visibility.py +0 -0
- {appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_user_tracking.py +0 -0
|
@@ -34,9 +34,18 @@ GOOGLE_CLIENT_ID=
|
|
|
34
34
|
GOOGLE_CLIENT_SECRET=
|
|
35
35
|
GOOGLE_DISCOVERY_URL=https://accounts.google.com/.well-known/openid-configuration
|
|
36
36
|
|
|
37
|
-
# SMTP Email Configuration (
|
|
37
|
+
# SMTP Email Configuration (Required for Magic Link email delivery in production)
|
|
38
38
|
SMTP_SERVER=
|
|
39
39
|
SMTP_PORT=587
|
|
40
40
|
SMTP_USER=
|
|
41
41
|
SMTP_PASSWORD=
|
|
42
42
|
MAIL_DEFAULT_SENDER=noreply@appmanager.local
|
|
43
|
+
|
|
44
|
+
# Developer Magic Login (Set to true to show one-click login button when SMTP is disabled)
|
|
45
|
+
ALLOW_DEV_MAGIC_LOGIN=false
|
|
46
|
+
|
|
47
|
+
# Role & Admin Provisioning
|
|
48
|
+
# FIRST_USER_IS_ADMIN: If true, the very first user who registers will be granted the admin role
|
|
49
|
+
FIRST_USER_IS_ADMIN=true
|
|
50
|
+
# ADMIN_EMAILS: Comma-separated list of emails that automatically receive admin role upon registration
|
|
51
|
+
ADMIN_EMAILS=
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **`appmanager-server`** will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## [0.2.1] - 2026-08-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Configurable Dev Magic Login (`ALLOW_DEV_MAGIC_LOGIN`)**: Prevents showing one-click developer login buttons in production when SMTP is unconfigured unless explicitly permitted.
|
|
14
|
+
- **Configurable Admin Role Provisioning (`ADMIN_EMAILS` & `FIRST_USER_IS_ADMIN`)**:
|
|
15
|
+
- `ADMIN_EMAILS`: Specify comma-separated list of admin email addresses.
|
|
16
|
+
- `FIRST_USER_IS_ADMIN`: Controls whether the very first registered user in a blank database receives the admin role (defaults to `true`).
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Improved error messaging on `/auth/login` when SMTP is unconfigured.
|
|
20
|
+
- Expanded authentication test suite.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## [0.2.0] - 2026-08-28
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- Initial PyPI release under package name **`appmanager-server`**.
|
|
28
|
+
- CLI commands and aliases: `appmanager-server`, `appmgr-server`, `appmanager`, and `appmgr`.
|
|
29
|
+
- AST Security Scanner (`appmanager.security_scanner.SecurityScanner`) for ZIP and Git package pre-installation checks.
|
|
30
|
+
- Python 3.10-3.13 support.
|
|
31
|
+
- PyPI Trusted Publishing via GitHub Actions OIDC.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: appmanager-server
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Multi-tenant Flask sub-application portal & dynamic WSGI dispatcher with in-process isolation, telemetry, and automated health checks.
|
|
5
5
|
Author: AppManager Contributors
|
|
6
6
|
License: MIT
|
|
@@ -39,6 +39,25 @@ def login():
|
|
|
39
39
|
return render_template("auth/login.html", next=next_url, google_configured=google_configured)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
def resolve_initial_user_role(email: str) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Resolves the initial role ('admin' or 'user') for a newly created user:
|
|
45
|
+
1. Checks if email is in ADMIN_EMAILS.
|
|
46
|
+
2. If FIRST_USER_IS_ADMIN is True and no users exist in the database yet, returns 'admin'.
|
|
47
|
+
3. Defaults to 'user'.
|
|
48
|
+
"""
|
|
49
|
+
clean_email = email.strip().lower()
|
|
50
|
+
admin_emails = current_app.config.get("ADMIN_EMAILS", [])
|
|
51
|
+
if clean_email in [e.strip().lower() for e in admin_emails if e.strip()]:
|
|
52
|
+
return "admin"
|
|
53
|
+
|
|
54
|
+
if current_app.config.get("FIRST_USER_IS_ADMIN", True):
|
|
55
|
+
if User.query.count() == 0:
|
|
56
|
+
return "admin"
|
|
57
|
+
|
|
58
|
+
return "user"
|
|
59
|
+
|
|
60
|
+
|
|
42
61
|
@auth_bp.route("/magic-link", methods=["POST"])
|
|
43
62
|
def request_magic_link():
|
|
44
63
|
client_ip = request.remote_addr or "unknown"
|
|
@@ -55,11 +74,23 @@ def request_magic_link():
|
|
|
55
74
|
flash("Please enter a valid email address.", "warning")
|
|
56
75
|
return redirect(url_for("auth.login", next=next_url))
|
|
57
76
|
|
|
77
|
+
smtp_configured = bool(current_app.config.get("SMTP_SERVER"))
|
|
78
|
+
allow_dev_magic = current_app.config.get("ALLOW_DEV_MAGIC_LOGIN", False)
|
|
79
|
+
|
|
80
|
+
# If SMTP is not configured and developer preview is disabled, block and show error
|
|
81
|
+
if not smtp_configured and not allow_dev_magic:
|
|
82
|
+
flash(
|
|
83
|
+
"Email delivery (SMTP) is not configured on this server. "
|
|
84
|
+
"Please configure SMTP settings in .env (or enable ALLOW_DEV_MAGIC_LOGIN=true for local testing).",
|
|
85
|
+
"danger",
|
|
86
|
+
)
|
|
87
|
+
return redirect(url_for("auth.login", next=next_url))
|
|
88
|
+
|
|
58
89
|
magic_url = create_magic_link(email)
|
|
59
90
|
|
|
60
|
-
# In dev mode
|
|
91
|
+
# In dev mode with ALLOW_DEV_MAGIC_LOGIN=True, pass the magic link URL to the template for quick login
|
|
61
92
|
dev_magic_url = None
|
|
62
|
-
if not
|
|
93
|
+
if not smtp_configured and allow_dev_magic:
|
|
63
94
|
dev_magic_url = magic_url
|
|
64
95
|
|
|
65
96
|
return render_template(
|
|
@@ -99,9 +130,7 @@ def verify_magic():
|
|
|
99
130
|
# Find or create user
|
|
100
131
|
user = User.query.filter_by(email=magic_token.email).first()
|
|
101
132
|
if not user:
|
|
102
|
-
|
|
103
|
-
user_count = User.query.count()
|
|
104
|
-
user_role = "admin" if user_count == 0 else "user"
|
|
133
|
+
user_role = resolve_initial_user_role(magic_token.email)
|
|
105
134
|
user = User(
|
|
106
135
|
email=magic_token.email,
|
|
107
136
|
name=magic_token.email.split("@")[0].capitalize(),
|
|
@@ -128,10 +157,7 @@ def verify_magic():
|
|
|
128
157
|
jwt_token = generate_jwt(user)
|
|
129
158
|
flash(f"Welcome back, {user.name}!", "success")
|
|
130
159
|
|
|
131
|
-
|
|
132
|
-
target_redirect = next_url if is_safe_redirect_url(next_url) else url_for("auth.profile")
|
|
133
|
-
|
|
134
|
-
response = make_response(redirect(target_redirect))
|
|
160
|
+
response = make_response(redirect(url_for("auth.profile")))
|
|
135
161
|
|
|
136
162
|
response.set_cookie(
|
|
137
163
|
JWT_COOKIE_NAME,
|
|
@@ -148,7 +174,7 @@ def verify_magic():
|
|
|
148
174
|
def google_login():
|
|
149
175
|
auth_url, error = get_google_auth_url()
|
|
150
176
|
if error:
|
|
151
|
-
flash(f"Google
|
|
177
|
+
flash(f"Google Login Error: {error}", "danger")
|
|
152
178
|
return redirect(url_for("auth.login"))
|
|
153
179
|
return redirect(auth_url)
|
|
154
180
|
|
|
@@ -170,11 +196,18 @@ def google_callback():
|
|
|
170
196
|
|
|
171
197
|
user = User.query.filter((User.google_id == google_id) | (User.email == email)).first()
|
|
172
198
|
if not user:
|
|
173
|
-
|
|
174
|
-
user_role = "admin" if user_count == 0 else "user"
|
|
199
|
+
user_role = resolve_initial_user_role(email)
|
|
175
200
|
user = User(email=email, name=name, google_id=google_id, role=user_role)
|
|
176
201
|
db.session.add(user)
|
|
177
202
|
db.session.commit()
|
|
203
|
+
|
|
204
|
+
# If non-admin user, assign default permissions for active apps
|
|
205
|
+
if user_role != "admin":
|
|
206
|
+
active_apps = InstalledApp.query.filter_by(is_active=True).all()
|
|
207
|
+
for app in active_apps:
|
|
208
|
+
perm = UserAppPermission(user_id=user.id, app_id=app.id, can_access=True)
|
|
209
|
+
db.session.add(perm)
|
|
210
|
+
db.session.commit()
|
|
178
211
|
else:
|
|
179
212
|
if not user.google_id:
|
|
180
213
|
user.google_id = google_id
|
|
@@ -48,6 +48,21 @@ class Config:
|
|
|
48
48
|
MAIL_DEFAULT_SENDER = os.getenv("MAIL_DEFAULT_SENDER", "noreply@appmanager.local")
|
|
49
49
|
APP_BASE_URL = os.getenv("APP_BASE_URL", "http://localhost:5000")
|
|
50
50
|
|
|
51
|
+
# Developer & Admin Provisioning Settings
|
|
52
|
+
ALLOW_DEV_MAGIC_LOGIN = os.getenv("ALLOW_DEV_MAGIC_LOGIN", "false").lower() in (
|
|
53
|
+
"true",
|
|
54
|
+
"1",
|
|
55
|
+
"yes",
|
|
56
|
+
)
|
|
57
|
+
FIRST_USER_IS_ADMIN = os.getenv("FIRST_USER_IS_ADMIN", "true").lower() in (
|
|
58
|
+
"true",
|
|
59
|
+
"1",
|
|
60
|
+
"yes",
|
|
61
|
+
)
|
|
62
|
+
ADMIN_EMAILS = [
|
|
63
|
+
e.strip().lower() for e in os.getenv("ADMIN_EMAILS", "").split(",") if e.strip()
|
|
64
|
+
]
|
|
65
|
+
|
|
51
66
|
# Security settings
|
|
52
67
|
SESSION_COOKIE_HTTPONLY = True
|
|
53
68
|
SESSION_COOKIE_SAMESITE = os.getenv("SESSION_COOKIE_SAMESITE", "Lax")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: appmanager-server
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Multi-tenant Flask sub-application portal & dynamic WSGI dispatcher with in-process isolation, telemetry, and automated health checks.
|
|
5
5
|
Author: AppManager Contributors
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "appmanager-server"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Multi-tenant Flask sub-application portal & dynamic WSGI dispatcher with in-process isolation, telemetry, and automated health checks."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -17,6 +17,7 @@ def app():
|
|
|
17
17
|
"INSTALLED_APPS_DIR": "/tmp/test_installed_apps",
|
|
18
18
|
"TEMP_UPLOAD_DIR": "/tmp/test_uploads",
|
|
19
19
|
"BASE_DIR": "/tmp",
|
|
20
|
+
"ALLOW_DEV_MAGIC_LOGIN": True, # Default to True in base test fixture
|
|
20
21
|
}
|
|
21
22
|
)
|
|
22
23
|
with app.app_context():
|
|
@@ -31,10 +32,11 @@ def client(app):
|
|
|
31
32
|
return app.test_client()
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
def
|
|
35
|
-
# 1. Request magic link
|
|
35
|
+
def test_magic_link_flow_dev_mode(client, app):
|
|
36
|
+
# 1. Request magic link with ALLOW_DEV_MAGIC_LOGIN=True
|
|
36
37
|
res = client.post("/auth/magic-link", data={"email": "test@example.com"})
|
|
37
38
|
assert res.status_code == 200
|
|
39
|
+
assert b"Developer Mode Preview" in res.data
|
|
38
40
|
|
|
39
41
|
# 2. Check token in DB
|
|
40
42
|
with app.app_context():
|
|
@@ -54,6 +56,49 @@ def test_magic_link_flow(client, app):
|
|
|
54
56
|
assert user.role == "admin"
|
|
55
57
|
|
|
56
58
|
|
|
59
|
+
def test_magic_link_fails_when_smtp_and_dev_mode_disabled(app):
|
|
60
|
+
app.config["ALLOW_DEV_MAGIC_LOGIN"] = False
|
|
61
|
+
app.config["SMTP_SERVER"] = ""
|
|
62
|
+
client = app.test_client()
|
|
63
|
+
|
|
64
|
+
res = client.post("/auth/magic-link", data={"email": "user@example.com"}, follow_redirects=True)
|
|
65
|
+
assert res.status_code == 200
|
|
66
|
+
assert b"Email delivery (SMTP) is not configured" in res.data
|
|
67
|
+
|
|
68
|
+
with app.app_context():
|
|
69
|
+
token = MagicLinkToken.query.filter_by(email="user@example.com").first()
|
|
70
|
+
assert token is None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_admin_emails_config_provisioning(client, app):
|
|
74
|
+
app.config["ADMIN_EMAILS"] = ["designated-admin@example.com"]
|
|
75
|
+
app.config["FIRST_USER_IS_ADMIN"] = False
|
|
76
|
+
|
|
77
|
+
# First user who is NOT in ADMIN_EMAILS
|
|
78
|
+
client.post("/auth/magic-link", data={"email": "regular@example.com"})
|
|
79
|
+
with app.app_context():
|
|
80
|
+
t1 = MagicLinkToken.query.filter_by(email="regular@example.com").first()
|
|
81
|
+
token1 = t1.token
|
|
82
|
+
|
|
83
|
+
client.get(f"/auth/verify-magic?token={token1}")
|
|
84
|
+
with app.app_context():
|
|
85
|
+
u1 = User.query.filter_by(email="regular@example.com").first()
|
|
86
|
+
assert u1 is not None
|
|
87
|
+
assert u1.role == "user"
|
|
88
|
+
|
|
89
|
+
# Second user who IS in ADMIN_EMAILS
|
|
90
|
+
client.post("/auth/magic-link", data={"email": "designated-admin@example.com"})
|
|
91
|
+
with app.app_context():
|
|
92
|
+
t2 = MagicLinkToken.query.filter_by(email="designated-admin@example.com").first()
|
|
93
|
+
token2 = t2.token
|
|
94
|
+
|
|
95
|
+
client.get(f"/auth/verify-magic?token={token2}")
|
|
96
|
+
with app.app_context():
|
|
97
|
+
u2 = User.query.filter_by(email="designated-admin@example.com").first()
|
|
98
|
+
assert u2 is not None
|
|
99
|
+
assert u2.role == "admin"
|
|
100
|
+
|
|
101
|
+
|
|
57
102
|
def test_second_user_is_regular_user(client, app):
|
|
58
103
|
# Create first user
|
|
59
104
|
with app.app_context():
|
|
@@ -12,7 +12,7 @@ from appmanager.cli import main
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def test_package_exports():
|
|
15
|
-
assert __version__ == "0.2.
|
|
15
|
+
assert __version__ == "0.2.1"
|
|
16
16
|
assert callable(create_app)
|
|
17
17
|
assert callable(create_dispatchable_app)
|
|
18
18
|
assert DynamicAppDispatcherMiddleware is not None
|
|
@@ -25,7 +25,7 @@ def test_cli_version(capsys):
|
|
|
25
25
|
main(["--version"])
|
|
26
26
|
assert exc.value.code == 0
|
|
27
27
|
captured = capsys.readouterr()
|
|
28
|
-
assert "0.2.
|
|
28
|
+
assert "0.2.1" in captured.out or "0.2.1" in captured.err
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def test_cli_help(capsys):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/fonts/fonts.css
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/static/vendor/htmx/htmx.min.js
RENAMED
|
File without changes
|
{appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/_member_drawer.html
RENAMED
|
File without changes
|
{appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/_members_table.html
RENAMED
|
File without changes
|
{appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/app_detail.html
RENAMED
|
File without changes
|
{appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/dashboard.html
RENAMED
|
File without changes
|
|
File without changes
|
{appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager/templates/admin/permissions.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager_server.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager_server.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{appmanager_server-0.2.0 → appmanager_server-0.2.1}/appmanager_server.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appmanager_server-0.2.0 → appmanager_server-0.2.1}/tests/test_user_elevation_and_visibility.py
RENAMED
|
File without changes
|
|
File without changes
|