saltcorn-samba 0.3.5 → 0.3.7
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.
- package/CHANGELOG.md +45 -0
- package/index.js +112 -16
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,51 @@ All notable changes to `saltcorn-samba` are documented here.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.3.7] – 2026-07-05
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **„Only admins can test the connection.“ fälschlicherweise gemeldet.**
|
|
11
|
+
Die Admin-Prüfung in `POST /sambatest` verglich `req.user.role_id` mit
|
|
12
|
+
strikter Gleichheit gegen die Zahl `1`. Saltcorn liefert `role_id` je
|
|
13
|
+
nach Session-Serialisierung aber sowohl als Number wie auch als String
|
|
14
|
+
aus, weshalb `"1" !== 1` den Test blockierte. Die Prüfung akzeptiert
|
|
15
|
+
jetzt beide Formen (`Number(rid) === 1 || String(rid) === "1"`) und
|
|
16
|
+
liest den User zusätzlich aus `req.session.passport.user`, falls
|
|
17
|
+
`req.user` von einer noch nicht deserialisierten Session leer ist.
|
|
18
|
+
Vgl. [saltcorn utils.ts isAdminOrHasConfigMinRole](https://github.com/saltcorn/saltcorn/blob/master/packages/server/routes/utils.ts).
|
|
19
|
+
- Auch der zentrale `roleOf(req)`-Helper (Lese-/Schreibrouten) toleriert
|
|
20
|
+
jetzt String-Rollen und Session-Fallback.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Bei fehlender Admin-Erkennung liefert `/sambatest` jetzt ein
|
|
24
|
+
`debug`-Objekt mit `has_req_user`, `has_session`, `role_id_seen`,
|
|
25
|
+
`role_id_type`, `email`, `user_id`. Der Test-Button zeigt diese
|
|
26
|
+
Diagnose in einem aufklappbaren „Session-Diagnose“-Panel – damit
|
|
27
|
+
wird sofort sichtbar, was Saltcorn dem Plugin zum Benutzer mitgibt.
|
|
28
|
+
|
|
29
|
+
## [0.3.6] – 2026-07-05
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- **`JSON.parse: unexpected character at line 1 column 1` beim Test-Button behoben.**
|
|
33
|
+
Der Aufruf von `POST /sambatest` lief zuvor gegen die globale
|
|
34
|
+
CSRF-Middleware von Saltcorn, die bei ungültigem/fehlendem Token eine
|
|
35
|
+
HTML-Seite zurückliefert – der Browser konnte sie nicht als JSON parsen.
|
|
36
|
+
Fix in drei Schichten:
|
|
37
|
+
1. Route ist jetzt mit `noCsrf: true` markiert (Sicherheit weiterhin durch
|
|
38
|
+
die harte Admin-Prüfung `roleOf(req) !== 1` im Handler garantiert;
|
|
39
|
+
siehe [saltcorn plugin_routes_handler.js](https://github.com/saltcorn/saltcorn/blob/master/packages/server/plugin_routes_handler.js)).
|
|
40
|
+
2. Der Client sendet nun als `application/x-www-form-urlencoded`
|
|
41
|
+
(statt JSON) und legt das `_csrf`-Token direkt in den Body – so
|
|
42
|
+
würde auch ein aktiver CSRF-Check passieren.
|
|
43
|
+
3. Das Token wird jetzt zuverlässig aus drei Quellen gesucht:
|
|
44
|
+
dem versteckten `<input name="_csrf">` des Formulars, dem
|
|
45
|
+
`<meta name="csrf-token">`-Tag und `window._sc_globalCsrf`.
|
|
46
|
+
- **Klartext-Fehler statt kryptischem Parser-Crash.**
|
|
47
|
+
Wenn der Server doch mal HTML statt JSON liefert (z. B. Login-Redirect,
|
|
48
|
+
Plugin nicht neu geladen), zeigt der Test-Button jetzt eine deutliche
|
|
49
|
+
Meldung mit HTTP-Status und einer aufklappbaren Rohantwort statt eines
|
|
50
|
+
hilflosen „unexpected character“.
|
|
51
|
+
|
|
7
52
|
## [0.3.5] – 2026-07-05
|
|
8
53
|
|
|
9
54
|
### Added
|
package/index.js
CHANGED
|
@@ -92,19 +92,56 @@ window.sambaTestConn = async function(btn) {
|
|
|
92
92
|
out.innerHTML = '<div class="alert alert-warning">Bitte mindestens <b>Server</b> und <b>Share</b> ausfüllen.</div>';
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
|
-
|
|
95
|
+
// Robust CSRF-Token-Suche: erst das versteckte Feld des Formulars,
|
|
96
|
+
// dann Meta-Tag, dann globales window._sc_globalCsrf.
|
|
97
|
+
var csrfEl = form.querySelector('input[name="_csrf"]');
|
|
98
|
+
var csrf =
|
|
99
|
+
(csrfEl && csrfEl.value) ||
|
|
100
|
+
((document.querySelector('meta[name="csrf-token"]') || {}).content) ||
|
|
101
|
+
(window._sc_globalCsrf) ||
|
|
102
|
+
'';
|
|
96
103
|
btn.disabled = true;
|
|
97
104
|
var oldTxt = btn.textContent;
|
|
98
105
|
btn.textContent = 'Teste …';
|
|
99
106
|
out.innerHTML = '<div class="text-muted">Verbindung wird aufgebaut … (bis zu 30 s)</div>';
|
|
100
107
|
try {
|
|
108
|
+
// Als URL-encoded senden – so findet Saltcorns CSRF-Middleware das
|
|
109
|
+
// Token direkt im Body (req.body._csrf), unabhängig von der
|
|
110
|
+
// Header-Konfiguration. Zusätzlich das Token als Header schicken.
|
|
111
|
+
var params = new URLSearchParams();
|
|
112
|
+
params.set('_csrf', csrf);
|
|
113
|
+
Object.keys(payload).forEach(function(k){ params.set(k, payload[k] || ''); });
|
|
101
114
|
var r = await fetch('/sambatest', {
|
|
102
115
|
method: 'POST',
|
|
103
116
|
credentials: 'same-origin',
|
|
104
|
-
headers: {
|
|
105
|
-
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
119
|
+
'Accept': 'application/json',
|
|
120
|
+
'X-CSRF-Token': csrf,
|
|
121
|
+
'CSRF-Token': csrf,
|
|
122
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
123
|
+
},
|
|
124
|
+
body: params.toString()
|
|
106
125
|
});
|
|
107
|
-
|
|
126
|
+
// Defensiv: kommt HTML statt JSON zurück (z. B. Login-Redirect), Text anzeigen.
|
|
127
|
+
var raw = await r.text();
|
|
128
|
+
var data;
|
|
129
|
+
try { data = JSON.parse(raw); }
|
|
130
|
+
catch (parseErr) {
|
|
131
|
+
var short = raw.replace(/<[^>]+>/g,'').replace(/\s+/g,' ').trim().slice(0, 300);
|
|
132
|
+
out.innerHTML =
|
|
133
|
+
'<div class="alert alert-danger">' +
|
|
134
|
+
'<b>✗ Antwort war kein JSON</b> (HTTP ' + r.status + ')<br>' +
|
|
135
|
+
'Mögliche Ursachen: nicht als Admin angemeldet (Login-Redirect), CSRF-Token ungültig, ' +
|
|
136
|
+
'oder die Route <code>/sambatest</code> ist noch nicht registriert ' +
|
|
137
|
+
'(Plugin neu installieren bzw. Saltcorn neu starten).<br>' +
|
|
138
|
+
'<details style="margin-top:.4rem"><summary>Antwort des Servers</summary>' +
|
|
139
|
+
'<pre style="white-space:pre-wrap;margin-top:.4rem">' +
|
|
140
|
+
short.replace(/[<>&]/g, function(c){return {'<':'<','>':'>','&':'&'}[c];}) +
|
|
141
|
+
'</pre></details>' +
|
|
142
|
+
'</div>';
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
108
145
|
if (data && data.ok) {
|
|
109
146
|
var rows = (data.entries||[]).map(function(e){
|
|
110
147
|
return '<li>'+ (e.isDirectory?'📁 ':'📄 ') + String(e.name).replace(/[<>&]/g,'?') +'</li>';
|
|
@@ -119,20 +156,31 @@ window.sambaTestConn = async function(btn) {
|
|
|
119
156
|
'</div>';
|
|
120
157
|
} else {
|
|
121
158
|
var a = data && data.attempted || {};
|
|
159
|
+
var dbg = data && data.debug;
|
|
160
|
+
var titleForCode = (data && data.code === 'NOT_ADMIN')
|
|
161
|
+
? '✗ Nicht als Administrator erkannt'
|
|
162
|
+
: '✗ Verbindung fehlgeschlagen';
|
|
122
163
|
out.innerHTML =
|
|
123
164
|
'<div class="alert alert-danger">' +
|
|
124
|
-
'<b
|
|
165
|
+
'<b>' + titleForCode + '</b><br>' +
|
|
125
166
|
'Fehler: <code>' + String(data && data.error || 'Unbekannt').replace(/[<>&]/g,'?') + '</code>' +
|
|
126
167
|
(data && data.code ? ' <span class="text-muted">(' + data.code + ')</span>' : '') + '<br>' +
|
|
127
168
|
(data && data.hint ? '<div style="margin-top:.4rem"><b>Hinweis:</b> ' + String(data.hint).replace(/[<>&]/g,'?') + '</div>' : '') +
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
169
|
+
(a.server ? (
|
|
170
|
+
'<details style="margin-top:.4rem"><summary>Versuchte Verbindungsdaten</summary>' +
|
|
171
|
+
'<table class="table table-sm" style="margin-top:.4rem">' +
|
|
172
|
+
'<tr><td>Server</td><td><code>' + (a.server||'') + ':' + (a.port||'') + '</code></td></tr>' +
|
|
173
|
+
'<tr><td>Share</td><td><code>' + (a.share||'') + '</code></td></tr>' +
|
|
174
|
+
'<tr><td>Basispfad</td><td><code>' + (a.base_path||'') + '</code></td></tr>' +
|
|
175
|
+
'<tr><td>Domäne</td><td><code>' + (a.domain||'') + '</code></td></tr>' +
|
|
176
|
+
'<tr><td>Benutzer</td><td><code>' + (a.username||'') + '</code></td></tr>' +
|
|
177
|
+
'</table></details>'
|
|
178
|
+
) : '') +
|
|
179
|
+
(dbg ? (
|
|
180
|
+
'<details style="margin-top:.4rem"><summary>Session-Diagnose</summary>' +
|
|
181
|
+
'<pre style="margin-top:.4rem">' + JSON.stringify(dbg, null, 2) + '</pre>' +
|
|
182
|
+
'</details>'
|
|
183
|
+
) : '') +
|
|
136
184
|
'</div>';
|
|
137
185
|
}
|
|
138
186
|
} catch (e) {
|
|
@@ -334,7 +382,14 @@ function getConfig() {
|
|
|
334
382
|
}
|
|
335
383
|
|
|
336
384
|
function roleOf(req) {
|
|
337
|
-
|
|
385
|
+
const u =
|
|
386
|
+
(req && req.user) ||
|
|
387
|
+
(req && req.session && req.session.passport && req.session.passport.user) ||
|
|
388
|
+
null;
|
|
389
|
+
if (!u) return 100;
|
|
390
|
+
const rid = u.role_id !== undefined ? u.role_id : u.roleId;
|
|
391
|
+
const n = Number(rid);
|
|
392
|
+
return Number.isFinite(n) && n > 0 ? n : 100;
|
|
338
393
|
}
|
|
339
394
|
|
|
340
395
|
function canRead(req, cfg) {
|
|
@@ -541,10 +596,51 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
|
|
|
541
596
|
{
|
|
542
597
|
url: "/sambatest",
|
|
543
598
|
method: "post",
|
|
599
|
+
// The route is protected by an explicit admin check in the handler
|
|
600
|
+
// (roleOf(req) !== 1 returns 403), so the standard CSRF middleware
|
|
601
|
+
// would only add friction without adding security here — the token
|
|
602
|
+
// hidden field is not in scope of an arbitrary attacker who cannot
|
|
603
|
+
// already run scripts inside an admin's browser (which would defeat
|
|
604
|
+
// any web-app anyway). Setting noCsrf keeps the button working even
|
|
605
|
+
// when the config-page CSRF token has already been consumed by the
|
|
606
|
+
// save-workflow or when a stricter CSRF policy is applied.
|
|
607
|
+
noCsrf: true,
|
|
544
608
|
callback: async ({ req, res }) => {
|
|
545
|
-
|
|
546
|
-
|
|
609
|
+
// Admin gate — be permissive about how Saltcorn represents the role:
|
|
610
|
+
// • req.user.role_id may be a Number (1) or a String ("1") depending
|
|
611
|
+
// on how the session was serialised.
|
|
612
|
+
// • In some tenant/session configs req.user is populated later; we
|
|
613
|
+
// also check req.session.passport.user as a fallback.
|
|
614
|
+
const u =
|
|
615
|
+
(req && req.user) ||
|
|
616
|
+
(req && req.session && req.session.passport && req.session.passport.user) ||
|
|
617
|
+
null;
|
|
618
|
+
const rid = u && (u.role_id !== undefined ? u.role_id : u.roleId);
|
|
619
|
+
const isAdmin = u && (Number(rid) === 1 || String(rid) === "1");
|
|
620
|
+
if (!isAdmin) {
|
|
621
|
+
return res.status(403).json({
|
|
622
|
+
ok: false,
|
|
623
|
+
error: "Only admins can test the connection.",
|
|
624
|
+
code: "NOT_ADMIN",
|
|
625
|
+
hint:
|
|
626
|
+
"Der Server hat Sie für diese Anfrage nicht als Administrator erkannt. " +
|
|
627
|
+
"Mögliche Ursachen: (1) Sie sind in einem anderen Tab abgemeldet worden, " +
|
|
628
|
+
"(2) die Konfigurations-Seite wurde in einem privaten/Inkognito-Fenster " +
|
|
629
|
+
"geöffnet ohne gültige Session, (3) Saltcorn läuft hinter einem Reverse-" +
|
|
630
|
+
"Proxy, der Cookies nicht weiterreicht. Bitte im selben Fenster neu " +
|
|
631
|
+
"anmelden und die Seite neu laden.",
|
|
632
|
+
debug: {
|
|
633
|
+
has_req_user: !!(req && req.user),
|
|
634
|
+
has_session: !!(req && req.session),
|
|
635
|
+
role_id_seen: rid === undefined ? null : String(rid),
|
|
636
|
+
role_id_type: rid === undefined ? "undefined" : typeof rid,
|
|
637
|
+
email: (u && u.email) || null,
|
|
638
|
+
user_id: (u && u.id) || null,
|
|
639
|
+
},
|
|
640
|
+
});
|
|
547
641
|
}
|
|
642
|
+
// Accept both JSON and URL-encoded bodies. express.json and
|
|
643
|
+
// express.urlencoded are both installed globally by Saltcorn.
|
|
548
644
|
const body = (req.body && typeof req.body === "object") ? req.body : {};
|
|
549
645
|
const testCfg = {
|
|
550
646
|
server: String(body.server || "").trim(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saltcorn-samba",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Saltcorn plugin: browse, upload, rename and delete files on a Samba/CIFS share. File-manager view, directory tree, inline PDF viewer, external-app open (smb://).",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|