cista 1.2.1__tar.gz → 1.4.0__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.
- {cista-1.2.1 → cista-1.4.0}/.gitignore +1 -1
- {cista-1.2.1 → cista-1.4.0}/PKG-INFO +6 -2
- {cista-1.2.1 → cista-1.4.0}/cista/__main__.py +7 -5
- {cista-1.2.1 → cista-1.4.0}/cista/_version.py +1 -1
- {cista-1.2.1 → cista-1.4.0}/cista/api.py +39 -10
- {cista-1.2.1 → cista-1.4.0}/cista/app.py +32 -10
- cista-1.4.0/cista/auth.py +498 -0
- {cista-1.2.1 → cista-1.4.0}/cista/config.py +9 -1
- cista-1.4.0/cista/frontend-build/assets/icons-DMD182WZ.js +1 -0
- cista-1.4.0/cista/frontend-build/assets/index-D4WrmH4f.js +32 -0
- cista-1.4.0/cista/frontend-build/assets/index-DQ2iYrs-.css +1 -0
- {cista-1.2.1/cista/wwwroot → cista-1.4.0/cista/frontend-build}/index.html +5 -5
- {cista-1.2.1 → cista-1.4.0}/cista/preview.py +71 -41
- cista-1.4.0/cista/sso.py +324 -0
- {cista-1.2.1 → cista-1.4.0}/cista/util/apphelpers.py +5 -2
- {cista-1.2.1 → cista-1.4.0}/cista/watching.py +2 -2
- {cista-1.2.1 → cista-1.4.0}/pyproject.toml +9 -3
- cista-1.2.1/cista/auth.py +0 -294
- cista-1.2.1/cista/wwwroot/assets/add-file-8c95935e.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/add-folder-86d38756.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/arrow-75fe0244.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/arrows-h-7cc7ed45.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/arrows-v-6ba20f93.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/check-2847ad21.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/code-a7719923.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/copy-3449d27d.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/create-file-a1cd75fd.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/create-folder-59db9ded.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/cross-38ada808.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/disk-39a6808b.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/download-dfa3cb2c.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/exclamation-1b2064ee.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/eye-8ad3b932.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/find-7e0bf1e0.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/fullscreen-e21f0ea1.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/github-33a0fb51.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/index-1cae47bc.css +0 -1
- cista-1.2.1/cista/wwwroot/assets/index-5b083aad.js +0 -28
- cista-1.2.1/cista/wwwroot/assets/info-f30cd4b4.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/link-3a6a01b8.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/logo-a02885a3.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/loop-17dda54a.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/menu-e088a6cc.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/next-c83214f4.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/open-bdcf30c3.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/paste-b1ebeeae.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/pause-c44d983d.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/pencil-70ceb3cc.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/play-38e640b3.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/plus-d2e20553.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/previous-78dea9b8.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/reload-61c8cee7.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/rename-202f8bc5.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/scissors-9235051c.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/shuffle-fd56ec51.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/signin-494e0573.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/signout-4047ccde.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/skip-bb8e721d.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/spinner-8c3861ee.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/stop-d670a372.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/trash-6a339a1b.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/triangle-9358f5e4.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/unfullscreen-792a694f.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/up-arrow-f518550f.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/upload-cloud-5f1a71c5.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/user-2d17adce.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/user-cog-5cefd313.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/volume-high-151aaf83.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/volume-low-8ffa3894.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/volume-medium-7b38c87d.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/volume-mute-4e81ae44.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/window-cross-ce8f66c3.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/window-dec8f28e.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/wordwrap-4618846d.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/zoomin-d821154d.js +0 -1
- cista-1.2.1/cista/wwwroot/assets/zoomout-faf726ac.js +0 -1
- {cista-1.2.1 → cista-1.4.0}/README.md +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/__init__.py +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/droppy.py +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/fileio.py +0 -0
- /cista-1.2.1/cista/wwwroot/assets/logo-97d1d7eb.svg → /cista-1.4.0/cista/frontend-build/assets/logo-ctv8tVwU.svg +0 -0
- {cista-1.2.1/cista/wwwroot → cista-1.4.0/cista/frontend-build}/robots.txt +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/protocol.py +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/serve.py +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/server80.py +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/session.py +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/util/__init__.py +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/util/asynclink.py +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/util/filename.py +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/util/lrucache.py +0 -0
- {cista-1.2.1 → cista-1.4.0}/cista/util/pwgen.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cista
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Dropbox-like file server with modern web interface
|
|
5
5
|
Project-URL: Homepage, https://git.zi.fi/Vasanko/cista-storage
|
|
6
6
|
Author: Vasanko
|
|
@@ -17,6 +17,10 @@ Requires-Dist: argon2-cffi>=25.1.0
|
|
|
17
17
|
Requires-Dist: av>=15.0.0
|
|
18
18
|
Requires-Dist: blake3>=1.0.5
|
|
19
19
|
Requires-Dist: docopt>=0.6.2
|
|
20
|
+
Requires-Dist: fastapi-vue>=0.5.1
|
|
21
|
+
Requires-Dist: fastapi[standard]>=0.128.0
|
|
22
|
+
Requires-Dist: html5tagger>=1.3.0
|
|
23
|
+
Requires-Dist: httpx>=0.28.0
|
|
20
24
|
Requires-Dist: inotify>=0.2.12
|
|
21
25
|
Requires-Dist: msgspec>=0.19.0
|
|
22
26
|
Requires-Dist: natsort>=8.4.0
|
|
@@ -26,7 +30,7 @@ Requires-Dist: pillow-heif>=1.1.0
|
|
|
26
30
|
Requires-Dist: pillow>=11.3.0
|
|
27
31
|
Requires-Dist: pyjwt>=2.10.1
|
|
28
32
|
Requires-Dist: pymupdf>=1.26.3
|
|
29
|
-
Requires-Dist: sanic>=25.
|
|
33
|
+
Requires-Dist: sanic>=25.12.0
|
|
30
34
|
Requires-Dist: setproctitle>=1.3.6
|
|
31
35
|
Requires-Dist: stream-zip>=0.0.83
|
|
32
36
|
Requires-Dist: tomli-w>=1.2.0
|
|
@@ -42,13 +42,17 @@ Options:
|
|
|
42
42
|
--import-droppy Import Droppy config from ~/.droppy/config
|
|
43
43
|
--dev Developer mode (reloads, friendlier crashes, more logs)
|
|
44
44
|
|
|
45
|
-
Listen address
|
|
46
|
-
|
|
45
|
+
Listen address and path are preserved in config,
|
|
46
|
+
and only config dir and dev mode need to be specified on subsequent runs.
|
|
47
47
|
|
|
48
48
|
User management:
|
|
49
49
|
--user NAME Create or modify user
|
|
50
50
|
--privileged Give the user full admin rights
|
|
51
51
|
--password Reset password
|
|
52
|
+
|
|
53
|
+
Environment:
|
|
54
|
+
PASKIA_BACKEND_URL Paskia single sign-on (e.g. http://localhost:4401)
|
|
55
|
+
https://git.zi.fi/leovasanko/paskia
|
|
52
56
|
"""
|
|
53
57
|
|
|
54
58
|
first_time_help = """\
|
|
@@ -107,6 +111,7 @@ def _main():
|
|
|
107
111
|
f"Importing Droppy: First remove the existing configuration:\n rm {config.conffile}",
|
|
108
112
|
)
|
|
109
113
|
settings = droppy.readconf()
|
|
114
|
+
# Droppy's public flag is kept as-is (same name in our config)
|
|
110
115
|
if path:
|
|
111
116
|
settings["path"] = path
|
|
112
117
|
elif not exists:
|
|
@@ -115,9 +120,6 @@ def _main():
|
|
|
115
120
|
settings["listen"] = listen
|
|
116
121
|
elif not exists:
|
|
117
122
|
settings["listen"] = ":8000"
|
|
118
|
-
if not exists and not import_droppy:
|
|
119
|
-
# We have no users, so make it public
|
|
120
|
-
settings["public"] = True
|
|
121
123
|
operation = config.update_config(settings)
|
|
122
124
|
sys.stderr.write(f"Config {operation}: {config.conffile}\n")
|
|
123
125
|
# Prepare to serve
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# This file is automatically generated by hatch build.
|
|
2
|
-
__version__ = '1.
|
|
2
|
+
__version__ = '1.4.0'
|
|
@@ -3,9 +3,10 @@ import typing
|
|
|
3
3
|
from secrets import token_bytes
|
|
4
4
|
|
|
5
5
|
import msgspec
|
|
6
|
-
from sanic import Blueprint
|
|
6
|
+
from sanic import Blueprint, json
|
|
7
|
+
from sanic.exceptions import BadRequest
|
|
7
8
|
|
|
8
|
-
from cista import __version__, config, watching
|
|
9
|
+
from cista import __version__, auth, config, sso, watching
|
|
9
10
|
from cista.fileio import FileServer
|
|
10
11
|
from cista.protocol import ControlTypes, FileRange, StatusMsg
|
|
11
12
|
from cista.util.apphelpers import asend, websocket_wrapper
|
|
@@ -15,12 +16,12 @@ fileserver = FileServer()
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@bp.before_server_start
|
|
18
|
-
async def start_fileserver(app
|
|
19
|
+
async def start_fileserver(app):
|
|
19
20
|
await fileserver.start()
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
@bp.after_server_stop
|
|
23
|
-
async def stop_fileserver(app
|
|
24
|
+
async def stop_fileserver(app):
|
|
24
25
|
await fileserver.stop()
|
|
25
26
|
|
|
26
27
|
|
|
@@ -92,6 +93,23 @@ async def control(req, ws):
|
|
|
92
93
|
@bp.websocket("watch")
|
|
93
94
|
@websocket_wrapper
|
|
94
95
|
async def watch(req, ws):
|
|
96
|
+
# Build user info from either built-in auth or SSO
|
|
97
|
+
user_info = None
|
|
98
|
+
if sso_user := getattr(req.ctx, "sso_user", None):
|
|
99
|
+
# SSO auth (paskia mode): extract from validation response
|
|
100
|
+
ctx = sso_user.get("ctx", {})
|
|
101
|
+
perms = ctx.get("permissions", [])
|
|
102
|
+
user_info = {
|
|
103
|
+
"username": ctx.get("user", {}).get("display_name", ""),
|
|
104
|
+
"privileged": "cista:admin" in perms,
|
|
105
|
+
}
|
|
106
|
+
elif req.ctx.user:
|
|
107
|
+
# Built-in auth: use local user database
|
|
108
|
+
user_info = {
|
|
109
|
+
"username": req.ctx.username,
|
|
110
|
+
"privileged": req.ctx.user.privileged,
|
|
111
|
+
}
|
|
112
|
+
|
|
95
113
|
await ws.send(
|
|
96
114
|
msgspec.json.encode(
|
|
97
115
|
{
|
|
@@ -99,13 +117,9 @@ async def watch(req, ws):
|
|
|
99
117
|
"name": config.config.name or config.config.path.name,
|
|
100
118
|
"version": __version__,
|
|
101
119
|
"public": config.config.public,
|
|
120
|
+
"paskia": sso.paskia_enabled(),
|
|
102
121
|
},
|
|
103
|
-
"user":
|
|
104
|
-
"username": req.ctx.username,
|
|
105
|
-
"privileged": req.ctx.user.privileged,
|
|
106
|
-
}
|
|
107
|
-
if req.ctx.user
|
|
108
|
-
else None,
|
|
122
|
+
"user": user_info,
|
|
109
123
|
}
|
|
110
124
|
).decode()
|
|
111
125
|
)
|
|
@@ -136,3 +150,18 @@ def subscribe(uuid, ws):
|
|
|
136
150
|
watching.format_space(watching.state.space),
|
|
137
151
|
watching.format_root(watching.state.root),
|
|
138
152
|
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@bp.put("config/public")
|
|
156
|
+
async def update_public(request):
|
|
157
|
+
await auth.verify(request, privileged=True)
|
|
158
|
+
try:
|
|
159
|
+
public = request.json["public"]
|
|
160
|
+
if not isinstance(public, bool):
|
|
161
|
+
raise ValueError("public must be a boolean")
|
|
162
|
+
except KeyError:
|
|
163
|
+
raise BadRequest("Missing public field") from None
|
|
164
|
+
except ValueError as e:
|
|
165
|
+
raise BadRequest(str(e)) from None
|
|
166
|
+
config.update_config({"public": public})
|
|
167
|
+
return json({"message": "Public access setting updated", "public": public})
|
|
@@ -18,7 +18,7 @@ from setproctitle import setproctitle
|
|
|
18
18
|
from stream_zip import ZIP_AUTO, stream_zip
|
|
19
19
|
from zstandard import ZstdCompressor
|
|
20
20
|
|
|
21
|
-
from cista import auth, config, preview, session, watching
|
|
21
|
+
from cista import auth, config, preview, session, sso, watching
|
|
22
22
|
from cista.api import bp
|
|
23
23
|
from cista.util.apphelpers import handle_sanic_exception
|
|
24
24
|
|
|
@@ -26,7 +26,11 @@ from cista.util.apphelpers import handle_sanic_exception
|
|
|
26
26
|
sanic.helpers._ENTITY_HEADERS = frozenset()
|
|
27
27
|
|
|
28
28
|
app = Sanic("cista", strict_slashes=True)
|
|
29
|
-
|
|
29
|
+
# Register either SSO proxy or built-in auth routes based on PASKIA_BACKEND_URL
|
|
30
|
+
if sso.paskia_enabled():
|
|
31
|
+
app.blueprint(sso.bp) # SSO proxy for /auth/* routes
|
|
32
|
+
else:
|
|
33
|
+
app.blueprint(auth.bp) # Built-in auth routes
|
|
30
34
|
app.blueprint(preview.bp)
|
|
31
35
|
app.blueprint(bp)
|
|
32
36
|
app.exception(Exception)(handle_sanic_exception)
|
|
@@ -36,22 +40,23 @@ setproctitle("cista-main")
|
|
|
36
40
|
|
|
37
41
|
|
|
38
42
|
@app.before_server_start
|
|
39
|
-
async def main_start(app
|
|
43
|
+
async def main_start(app):
|
|
40
44
|
config.load_config()
|
|
41
45
|
setproctitle(f"cista {config.config.path.name}")
|
|
42
46
|
workers = max(2, min(8, cpu_count()))
|
|
43
47
|
app.ctx.threadexec = ThreadPoolExecutor(
|
|
44
48
|
max_workers=workers, thread_name_prefix="cista-ioworker"
|
|
45
49
|
)
|
|
46
|
-
watching.start(app
|
|
50
|
+
watching.start(app)
|
|
47
51
|
|
|
48
52
|
|
|
49
53
|
# Sanic sometimes fails to execute after_server_stop, so we do it before instead (potentially interrupting handlers)
|
|
50
54
|
@app.before_server_stop
|
|
51
|
-
async def main_stop(app
|
|
55
|
+
async def main_stop(app):
|
|
52
56
|
quit.set()
|
|
53
57
|
watching.stop(app)
|
|
54
58
|
app.ctx.threadexec.shutdown()
|
|
59
|
+
await sso.close_client()
|
|
55
60
|
logger.debug("Cista worker threads all finished")
|
|
56
61
|
|
|
57
62
|
|
|
@@ -74,10 +79,23 @@ async def use_session(req):
|
|
|
74
79
|
raise Forbidden("Invalid origin: Cross-Site requests not permitted")
|
|
75
80
|
|
|
76
81
|
|
|
82
|
+
@app.on_response
|
|
83
|
+
async def forward_sso_cookies(req, res):
|
|
84
|
+
"""Forward Set-Cookie headers from SSO validation to client."""
|
|
85
|
+
if cookies := getattr(req.ctx, "sso_cookies", None):
|
|
86
|
+
for cookie in cookies:
|
|
87
|
+
res.headers.add("set-cookie", cookie)
|
|
88
|
+
|
|
89
|
+
|
|
77
90
|
@app.before_server_start
|
|
78
|
-
def http_fileserver(app
|
|
91
|
+
def http_fileserver(app):
|
|
79
92
|
bp = Blueprint("fileserver")
|
|
80
|
-
|
|
93
|
+
|
|
94
|
+
@bp.on_request
|
|
95
|
+
async def verify_fileserver(request):
|
|
96
|
+
"""Verify access to file server routes."""
|
|
97
|
+
await auth.verify(request)
|
|
98
|
+
|
|
81
99
|
bp.static(
|
|
82
100
|
"/files/",
|
|
83
101
|
config.config.path,
|
|
@@ -93,9 +111,9 @@ www = {}
|
|
|
93
111
|
|
|
94
112
|
def _load_wwwroot(www):
|
|
95
113
|
wwwnew = {}
|
|
96
|
-
base = Path(__file__).with_name("
|
|
114
|
+
base = Path(__file__).with_name("frontend-build")
|
|
97
115
|
paths = [PurePath()]
|
|
98
|
-
zstd = ZstdCompressor(level=
|
|
116
|
+
zstd = ZstdCompressor(level=18)
|
|
99
117
|
while paths:
|
|
100
118
|
path = paths.pop(0)
|
|
101
119
|
current = base / path
|
|
@@ -211,7 +229,7 @@ async def wwwroot(req, path=""):
|
|
|
211
229
|
@app.route("/favicon.ico", methods=["GET", "HEAD"])
|
|
212
230
|
async def favicon(req):
|
|
213
231
|
# Browsers keep asking for it when viewing files (not HTML with icon link)
|
|
214
|
-
return redirect("/assets/logo-
|
|
232
|
+
return redirect("/assets/logo-ctv8tVwU.svg", status=308)
|
|
215
233
|
|
|
216
234
|
|
|
217
235
|
def get_files(wanted: set) -> list[tuple[PurePosixPath, Path]]:
|
|
@@ -239,6 +257,10 @@ def get_files(wanted: set) -> list[tuple[PurePosixPath, Path]]:
|
|
|
239
257
|
@app.get("/zip/<keys>/<zipfile:ext=zip>")
|
|
240
258
|
async def zip_download(req, keys, zipfile, ext):
|
|
241
259
|
"""Download a zip archive of the given keys"""
|
|
260
|
+
if config.config.authentication == "paskia":
|
|
261
|
+
await auth.verify_sso(req)
|
|
262
|
+
else:
|
|
263
|
+
auth.verify(req)
|
|
242
264
|
|
|
243
265
|
wanted = set(keys.split("+"))
|
|
244
266
|
files = get_files(wanted)
|