flowent 0.0.4__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.
- flowent-0.0.4/PKG-INFO +20 -0
- flowent-0.0.4/pyproject.toml +57 -0
- flowent-0.0.4/src/flowent/__init__.py +3 -0
- flowent-0.0.4/src/flowent/_version.py +7 -0
- flowent-0.0.4/src/flowent/access.py +247 -0
- flowent-0.0.4/src/flowent/agent.py +2808 -0
- flowent-0.0.4/src/flowent/assistant_commands.py +106 -0
- flowent-0.0.4/src/flowent/channels/__init__.py +3 -0
- flowent-0.0.4/src/flowent/channels/telegram.py +615 -0
- flowent-0.0.4/src/flowent/cli.py +85 -0
- flowent-0.0.4/src/flowent/config.py +14 -0
- flowent-0.0.4/src/flowent/dev.py +3 -0
- flowent-0.0.4/src/flowent/events.py +157 -0
- flowent-0.0.4/src/flowent/graph_runtime.py +60 -0
- flowent-0.0.4/src/flowent/graph_service.py +1346 -0
- flowent-0.0.4/src/flowent/image_assets.py +356 -0
- flowent-0.0.4/src/flowent/logging.py +155 -0
- flowent-0.0.4/src/flowent/main.py +124 -0
- flowent-0.0.4/src/flowent/mcp_service.py +1904 -0
- flowent-0.0.4/src/flowent/model_metadata.py +98 -0
- flowent-0.0.4/src/flowent/models/__init__.py +121 -0
- flowent-0.0.4/src/flowent/models/agent.py +33 -0
- flowent-0.0.4/src/flowent/models/base.py +24 -0
- flowent-0.0.4/src/flowent/models/blueprint.py +176 -0
- flowent-0.0.4/src/flowent/models/content.py +164 -0
- flowent-0.0.4/src/flowent/models/delta.py +44 -0
- flowent-0.0.4/src/flowent/models/event.py +51 -0
- flowent-0.0.4/src/flowent/models/graph.py +437 -0
- flowent-0.0.4/src/flowent/models/history.py +214 -0
- flowent-0.0.4/src/flowent/models/llm.py +61 -0
- flowent-0.0.4/src/flowent/models/message.py +27 -0
- flowent-0.0.4/src/flowent/models/tab.py +48 -0
- flowent-0.0.4/src/flowent/models/todo.py +10 -0
- flowent-0.0.4/src/flowent/network.py +146 -0
- flowent-0.0.4/src/flowent/prompts/__init__.py +67 -0
- flowent-0.0.4/src/flowent/prompts/common.py +250 -0
- flowent-0.0.4/src/flowent/prompts/steward.py +64 -0
- flowent-0.0.4/src/flowent/providers/__init__.py +23 -0
- flowent-0.0.4/src/flowent/providers/anthropic.py +468 -0
- flowent-0.0.4/src/flowent/providers/base_url.py +60 -0
- flowent-0.0.4/src/flowent/providers/configuration.py +182 -0
- flowent-0.0.4/src/flowent/providers/content.py +122 -0
- flowent-0.0.4/src/flowent/providers/errors.py +223 -0
- flowent-0.0.4/src/flowent/providers/gateway.py +169 -0
- flowent-0.0.4/src/flowent/providers/gemini.py +447 -0
- flowent-0.0.4/src/flowent/providers/headers.py +20 -0
- flowent-0.0.4/src/flowent/providers/management.py +96 -0
- flowent-0.0.4/src/flowent/providers/ollama.py +293 -0
- flowent-0.0.4/src/flowent/providers/openai.py +422 -0
- flowent-0.0.4/src/flowent/providers/openai_responses.py +655 -0
- flowent-0.0.4/src/flowent/providers/registry.py +144 -0
- flowent-0.0.4/src/flowent/providers/sse.py +31 -0
- flowent-0.0.4/src/flowent/providers/thinking.py +79 -0
- flowent-0.0.4/src/flowent/registry.py +73 -0
- flowent-0.0.4/src/flowent/role_management.py +255 -0
- flowent-0.0.4/src/flowent/routes/__init__.py +30 -0
- flowent-0.0.4/src/flowent/routes/access.py +48 -0
- flowent-0.0.4/src/flowent/routes/assistant.py +155 -0
- flowent-0.0.4/src/flowent/routes/image_assets.py +33 -0
- flowent-0.0.4/src/flowent/routes/mcp.py +125 -0
- flowent-0.0.4/src/flowent/routes/meta.py +28 -0
- flowent-0.0.4/src/flowent/routes/nodes.py +365 -0
- flowent-0.0.4/src/flowent/routes/prompts.py +46 -0
- flowent-0.0.4/src/flowent/routes/providers_route.py +364 -0
- flowent-0.0.4/src/flowent/routes/roles.py +207 -0
- flowent-0.0.4/src/flowent/routes/settings.py +324 -0
- flowent-0.0.4/src/flowent/routes/stats.py +229 -0
- flowent-0.0.4/src/flowent/routes/tabs.py +292 -0
- flowent-0.0.4/src/flowent/routes/ws.py +33 -0
- flowent-0.0.4/src/flowent/runtime.py +188 -0
- flowent-0.0.4/src/flowent/sandbox.py +45 -0
- flowent-0.0.4/src/flowent/security.py +42 -0
- flowent-0.0.4/src/flowent/settings.py +2467 -0
- flowent-0.0.4/src/flowent/settings_management.py +286 -0
- flowent-0.0.4/src/flowent/state_db.py +120 -0
- flowent-0.0.4/src/flowent/static/assets/AssistantPage-B3Xc08AS.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/ChannelsPage-ByLd28xk.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/HomePage-C0hAx9_l.js +3 -0
- flowent-0.0.4/src/flowent/static/assets/McpPage-DkrYLvBv.js +7 -0
- flowent-0.0.4/src/flowent/static/assets/PageScaffold-D4jO9ooX.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/PromptsPage-DWA7rRJd.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/ProvidersPage-PUWT8seJ.js +3 -0
- flowent-0.0.4/src/flowent/static/assets/RolesPage-CqcclGRw.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/SettingsPage-8tS2cJgX.js +3 -0
- flowent-0.0.4/src/flowent/static/assets/StatsPage-BX9khYzu.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/ToolsPage-9Tl9FdeD.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/alert-dialog-kFYVQ7oX.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/badge-74-3jsCg.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/constants-XUzFf6i1.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/datetime-m6_O_Ci9.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/dialog-BeGSweF6.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +6312 -0
- flowent-0.0.4/src/flowent/static/assets/graph-vendor-CHpVij2M.css +1 -0
- flowent-0.0.4/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +7 -0
- flowent-0.0.4/src/flowent/static/assets/index-BHC1Vhy8.css +1 -0
- flowent-0.0.4/src/flowent/static/assets/index-CL1ALZ3r.js +10 -0
- flowent-0.0.4/src/flowent/static/assets/layout.worker-jMHqAFbP.js +24 -0
- flowent-0.0.4/src/flowent/static/assets/markdown-vendor-DVdy_w12.js +29 -0
- flowent-0.0.4/src/flowent/static/assets/modelParams-CaHd0903.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/react-vendor-mEs_JJxa.js +9 -0
- flowent-0.0.4/src/flowent/static/assets/roles-2OLDeTc5.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/select-DL_LPeDj.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/shared-CMxbpLeQ.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/triState-DEr3NkXV.js +1 -0
- flowent-0.0.4/src/flowent/static/assets/ui-vendor-Dg9NNnWX.js +51 -0
- flowent-0.0.4/src/flowent/static/favicon.svg +4 -0
- flowent-0.0.4/src/flowent/static/index.html +36 -0
- flowent-0.0.4/src/flowent/stats_service.py +218 -0
- flowent-0.0.4/src/flowent/tools/__init__.py +201 -0
- flowent-0.0.4/src/flowent/tools/connect.py +156 -0
- flowent-0.0.4/src/flowent/tools/contacts.py +22 -0
- flowent-0.0.4/src/flowent/tools/create_agent.py +270 -0
- flowent-0.0.4/src/flowent/tools/create_tab.py +59 -0
- flowent-0.0.4/src/flowent/tools/delete_tab.py +39 -0
- flowent-0.0.4/src/flowent/tools/edit.py +142 -0
- flowent-0.0.4/src/flowent/tools/exec.py +117 -0
- flowent-0.0.4/src/flowent/tools/fetch.py +85 -0
- flowent-0.0.4/src/flowent/tools/idle.py +27 -0
- flowent-0.0.4/src/flowent/tools/list_roles.py +50 -0
- flowent-0.0.4/src/flowent/tools/list_tabs.py +96 -0
- flowent-0.0.4/src/flowent/tools/list_tools.py +24 -0
- flowent-0.0.4/src/flowent/tools/manage_prompts.py +102 -0
- flowent-0.0.4/src/flowent/tools/manage_providers.py +220 -0
- flowent-0.0.4/src/flowent/tools/manage_roles.py +275 -0
- flowent-0.0.4/src/flowent/tools/manage_settings.py +346 -0
- flowent-0.0.4/src/flowent/tools/mcp.py +199 -0
- flowent-0.0.4/src/flowent/tools/read.py +152 -0
- flowent-0.0.4/src/flowent/tools/send.py +50 -0
- flowent-0.0.4/src/flowent/tools/set_permissions.py +84 -0
- flowent-0.0.4/src/flowent/tools/sleep.py +41 -0
- flowent-0.0.4/src/flowent/tools/todo.py +51 -0
- flowent-0.0.4/src/flowent/workspace_store.py +479 -0
flowent-0.0.4/PKG-INFO
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flowent
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: A workflow orchestration platform for multi-agent collaboration.
|
|
5
|
+
Author: ImFeH2
|
|
6
|
+
Author-email: ImFeH2 <i@feh2.im>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Requires-Dist: curl-cffi>=0.15.0
|
|
9
|
+
Requires-Dist: fastapi[standard]>=0.136.1
|
|
10
|
+
Requires-Dist: httpx>=0.28.0
|
|
11
|
+
Requires-Dist: itsdangerous>=2.2.0
|
|
12
|
+
Requires-Dist: loguru>=0.7.0
|
|
13
|
+
Requires-Dist: pydantic-settings>=2.12.0
|
|
14
|
+
Requires-Dist: python-dotenv>=1.2.1
|
|
15
|
+
Requires-Dist: python-multipart>=0.0.22
|
|
16
|
+
Requires-Dist: uvicorn>=0.46.0
|
|
17
|
+
Requires-Python: >=3.12, <3.14
|
|
18
|
+
Project-URL: Homepage, https://github.com/ImFeH2/flowent
|
|
19
|
+
Project-URL: Issues, https://github.com/ImFeH2/flowent/issues
|
|
20
|
+
Project-URL: Repository, https://github.com/ImFeH2/flowent
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "flowent"
|
|
3
|
+
version = "0.0.4"
|
|
4
|
+
description = "A workflow orchestration platform for multi-agent collaboration."
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "ImFeH2", email = "i@feh2.im" }
|
|
7
|
+
]
|
|
8
|
+
requires-python = ">=3.12,<3.14"
|
|
9
|
+
license = "Apache-2.0"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"curl-cffi>=0.15.0",
|
|
12
|
+
"fastapi[standard]>=0.136.1",
|
|
13
|
+
"httpx>=0.28.0",
|
|
14
|
+
"itsdangerous>=2.2.0",
|
|
15
|
+
"loguru>=0.7.0",
|
|
16
|
+
"pydantic-settings>=2.12.0",
|
|
17
|
+
"python-dotenv>=1.2.1",
|
|
18
|
+
"python-multipart>=0.0.22",
|
|
19
|
+
"uvicorn>=0.46.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = [
|
|
24
|
+
"httpx>=0.28.1",
|
|
25
|
+
"mypy>=1.19.1",
|
|
26
|
+
"pre-commit>=4.5.1",
|
|
27
|
+
"pytest>=9.0.3",
|
|
28
|
+
"ruff>=0.15.12",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
flowent = "flowent:main"
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/ImFeH2/flowent"
|
|
36
|
+
Repository = "https://github.com/ImFeH2/flowent"
|
|
37
|
+
Issues = "https://github.com/ImFeH2/flowent/issues"
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = ["uv_build>=0.8.14,<0.9.0"]
|
|
41
|
+
build-backend = "uv_build"
|
|
42
|
+
|
|
43
|
+
[tool.ruff]
|
|
44
|
+
target-version = "py312"
|
|
45
|
+
|
|
46
|
+
[tool.ruff.lint]
|
|
47
|
+
select = ["E", "W", "F", "I", "UP", "B", "SIM", "N", "RUF"]
|
|
48
|
+
ignore = ["E501"]
|
|
49
|
+
|
|
50
|
+
[tool.mypy]
|
|
51
|
+
python_version = "3.12"
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
python_files = ["test_*.py"]
|
|
56
|
+
python_classes = ["Test*"]
|
|
57
|
+
python_functions = ["test_*"]
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import secrets
|
|
5
|
+
import threading
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from fastapi import Request
|
|
12
|
+
from loguru import logger
|
|
13
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
14
|
+
from starlette.responses import JSONResponse
|
|
15
|
+
from starlette.websockets import WebSocket
|
|
16
|
+
|
|
17
|
+
from flowent.settings import AccessSettings, Settings, get_settings, save_settings
|
|
18
|
+
|
|
19
|
+
ACCESS_SESSION_KEY = "admin_session_generation"
|
|
20
|
+
ACCESS_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
|
21
|
+
ACCESS_PUBLIC_PATHS = frozenset(
|
|
22
|
+
{
|
|
23
|
+
"/api/access/state",
|
|
24
|
+
"/api/access/login",
|
|
25
|
+
"/api/access/logout",
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class _AccessRuntimeState:
|
|
32
|
+
bootstrap_generated: bool = False
|
|
33
|
+
live_signature: tuple[bool, int, str] | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_runtime_state = _AccessRuntimeState()
|
|
37
|
+
_runtime_lock = threading.Lock()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _hash_access_code(code: str, salt: str) -> str:
|
|
41
|
+
payload = f"{salt}:{code}".encode()
|
|
42
|
+
return hashlib.sha256(payload).hexdigest()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def generate_access_code() -> str:
|
|
46
|
+
groups = [
|
|
47
|
+
"".join(secrets.choice(ACCESS_CODE_ALPHABET) for _ in range(4))
|
|
48
|
+
for _ in range(3)
|
|
49
|
+
]
|
|
50
|
+
return "-".join(groups)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_access_configured(access: AccessSettings) -> bool:
|
|
54
|
+
return bool(
|
|
55
|
+
access.code.strip() and access.code_hash.strip() and access.code_salt.strip()
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def set_access_code(settings: Settings, code: str) -> None:
|
|
60
|
+
salt = secrets.token_hex(16)
|
|
61
|
+
current_generation = settings.access.session_generation
|
|
62
|
+
session_signing_secret = settings.access.session_signing_secret
|
|
63
|
+
settings.access = AccessSettings(
|
|
64
|
+
code=code,
|
|
65
|
+
code_hash=_hash_access_code(code, salt),
|
|
66
|
+
code_salt=salt,
|
|
67
|
+
session_generation=1 if current_generation <= 0 else current_generation + 1,
|
|
68
|
+
session_signing_secret=session_signing_secret,
|
|
69
|
+
)
|
|
70
|
+
with _runtime_lock:
|
|
71
|
+
_runtime_state.bootstrap_generated = False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def clear_access_code(settings: Settings) -> bool:
|
|
75
|
+
was_configured = is_access_configured(settings.access)
|
|
76
|
+
next_generation = (
|
|
77
|
+
1
|
|
78
|
+
if settings.access.session_generation <= 0
|
|
79
|
+
else settings.access.session_generation + 1
|
|
80
|
+
)
|
|
81
|
+
settings.access = AccessSettings(
|
|
82
|
+
session_generation=next_generation,
|
|
83
|
+
session_signing_secret=settings.access.session_signing_secret,
|
|
84
|
+
)
|
|
85
|
+
with _runtime_lock:
|
|
86
|
+
_runtime_state.bootstrap_generated = False
|
|
87
|
+
return was_configured
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def verify_access_code(access: AccessSettings, code: str) -> bool:
|
|
91
|
+
if not is_access_configured(access):
|
|
92
|
+
return False
|
|
93
|
+
expected = _hash_access_code(code, access.code_salt)
|
|
94
|
+
return secrets.compare_digest(expected, access.code_hash)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def ensure_access_bootstrap(settings: Settings) -> str | None:
|
|
98
|
+
generated_code: str | None = None
|
|
99
|
+
if not is_access_configured(settings.access):
|
|
100
|
+
generated_code = generate_access_code()
|
|
101
|
+
set_access_code(settings, generated_code)
|
|
102
|
+
logger.warning("Flowent admin access code: {}", settings.access.code)
|
|
103
|
+
with _runtime_lock:
|
|
104
|
+
_runtime_state.bootstrap_generated = generated_code is not None
|
|
105
|
+
return generated_code
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def ensure_session_signing_secret(settings: Settings) -> bool:
|
|
109
|
+
if settings.access.session_signing_secret.strip():
|
|
110
|
+
return False
|
|
111
|
+
settings.access.session_signing_secret = secrets.token_urlsafe(32)
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _read_live_access_settings() -> AccessSettings:
|
|
116
|
+
from flowent.settings import _SETTINGS_FILE, _read_settings_file
|
|
117
|
+
|
|
118
|
+
settings_file = Path(_SETTINGS_FILE)
|
|
119
|
+
if settings_file.exists():
|
|
120
|
+
try:
|
|
121
|
+
settings, _ = _read_settings_file()
|
|
122
|
+
return settings.access
|
|
123
|
+
except Exception as exc:
|
|
124
|
+
logger.warning(
|
|
125
|
+
"Failed to read live access settings from {}: {}",
|
|
126
|
+
settings_file,
|
|
127
|
+
exc,
|
|
128
|
+
)
|
|
129
|
+
return get_settings().access
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _build_live_signature(access: AccessSettings) -> tuple[bool, int, str]:
|
|
133
|
+
return (
|
|
134
|
+
is_access_configured(access),
|
|
135
|
+
access.session_generation,
|
|
136
|
+
access.session_signing_secret,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def initialize_live_access_signature() -> None:
|
|
141
|
+
access = _read_live_access_settings()
|
|
142
|
+
with _runtime_lock:
|
|
143
|
+
_runtime_state.live_signature = _build_live_signature(access)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def refresh_live_access_signature() -> bool:
|
|
147
|
+
access = _read_live_access_settings()
|
|
148
|
+
next_signature = _build_live_signature(access)
|
|
149
|
+
with _runtime_lock:
|
|
150
|
+
previous_signature = _runtime_state.live_signature
|
|
151
|
+
_runtime_state.live_signature = next_signature
|
|
152
|
+
return previous_signature is not None and previous_signature != next_signature
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def is_authenticated_session(
|
|
156
|
+
session: Mapping[str, Any] | None,
|
|
157
|
+
access: AccessSettings | None = None,
|
|
158
|
+
) -> bool:
|
|
159
|
+
if session is None:
|
|
160
|
+
return False
|
|
161
|
+
current_access = access or _read_live_access_settings()
|
|
162
|
+
if not is_access_configured(current_access):
|
|
163
|
+
return False
|
|
164
|
+
raw_generation = session.get(ACCESS_SESSION_KEY)
|
|
165
|
+
if isinstance(raw_generation, bool) or not isinstance(raw_generation, int):
|
|
166
|
+
return False
|
|
167
|
+
return raw_generation == current_access.session_generation
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def build_access_state_payload(
|
|
171
|
+
session: Mapping[str, Any] | None,
|
|
172
|
+
) -> dict[str, object]:
|
|
173
|
+
access = _read_live_access_settings()
|
|
174
|
+
with _runtime_lock:
|
|
175
|
+
bootstrap_generated = _runtime_state.bootstrap_generated
|
|
176
|
+
configured = is_access_configured(access)
|
|
177
|
+
return {
|
|
178
|
+
"authenticated": is_authenticated_session(session, access),
|
|
179
|
+
"configured": configured,
|
|
180
|
+
"bootstrap_generated": configured and bootstrap_generated,
|
|
181
|
+
"requires_restart": not configured,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def access_request_is_public(path: str) -> bool:
|
|
186
|
+
return path in ACCESS_PUBLIC_PATHS
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class AccessControlMiddleware(BaseHTTPMiddleware):
|
|
190
|
+
async def dispatch(self, request: Request, call_next):
|
|
191
|
+
path = request.url.path
|
|
192
|
+
if not path.startswith("/api") or access_request_is_public(path):
|
|
193
|
+
return await call_next(request)
|
|
194
|
+
if is_authenticated_session(request.session):
|
|
195
|
+
return await call_next(request)
|
|
196
|
+
return JSONResponse({"detail": "Access denied"}, status_code=401)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
async def authorize_websocket(ws: WebSocket) -> bool:
|
|
200
|
+
if is_authenticated_session(ws.scope.get("session")):
|
|
201
|
+
return True
|
|
202
|
+
await ws.close(code=4401, reason="Access denied")
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def reset_local_access() -> str:
|
|
207
|
+
from flowent.events import event_bus
|
|
208
|
+
|
|
209
|
+
settings = get_settings()
|
|
210
|
+
clear_access_code(settings)
|
|
211
|
+
save_settings(settings)
|
|
212
|
+
initialize_live_access_signature()
|
|
213
|
+
event_bus.close_all_connections(code=4001, reason="Access session reset")
|
|
214
|
+
return (
|
|
215
|
+
"Access configuration cleared. Restart Flowent to generate a new access code."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def refresh_local_access() -> str:
|
|
220
|
+
from flowent.events import event_bus
|
|
221
|
+
|
|
222
|
+
settings = get_settings()
|
|
223
|
+
next_code = generate_access_code()
|
|
224
|
+
set_access_code(settings, next_code)
|
|
225
|
+
save_settings(settings)
|
|
226
|
+
initialize_live_access_signature()
|
|
227
|
+
event_bus.close_all_connections(code=4001, reason="Access session refreshed")
|
|
228
|
+
return f"Generated new access code: {next_code}"
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
__all__ = [
|
|
232
|
+
"ACCESS_SESSION_KEY",
|
|
233
|
+
"AccessControlMiddleware",
|
|
234
|
+
"authorize_websocket",
|
|
235
|
+
"build_access_state_payload",
|
|
236
|
+
"clear_access_code",
|
|
237
|
+
"ensure_access_bootstrap",
|
|
238
|
+
"ensure_session_signing_secret",
|
|
239
|
+
"initialize_live_access_signature",
|
|
240
|
+
"is_access_configured",
|
|
241
|
+
"is_authenticated_session",
|
|
242
|
+
"refresh_live_access_signature",
|
|
243
|
+
"refresh_local_access",
|
|
244
|
+
"reset_local_access",
|
|
245
|
+
"set_access_code",
|
|
246
|
+
"verify_access_code",
|
|
247
|
+
]
|