Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1cddb8ea7 | ||
|
|
9cbd93e1f5 | ||
|
|
d4cf2db347 | ||
|
|
d6bf80a68a | ||
|
|
679f91da2c |
No files matched your search
@@ -11,6 +11,24 @@ Version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html
|
||||
|
||||
---
|
||||
|
||||
## [1.7.4] — 2026-06-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Share modal kept stale input after creating a link** — clicking "Create" only cleared the label field; scope type, user email, date range, and expiry kept their values, so the next link silently inherited the previous link's scope settings. The form-reset logic from `openShareModal()` is now a shared `_resetShareForm()` helper called after every successful create (the generated link row stays visible for copying).
|
||||
|
||||
---
|
||||
|
||||
## [1.7.3] — 2026-06-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- **App restart no longer hops to a new port** — the in-app update restart (and any quick stop/start) left connections from the previous instance in TIME_WAIT, and the startup port probe did a plain `bind()` that treats TIME_WAIT as occupied — so the restarted app silently came up on 5101 and the browser's reload poll never found it. The probe now sets `SO_REUSEADDR` (matching how Werkzeug actually binds, so an actively listening port is still detected as occupied), and the requested port gets a 10-second grace period before the auto-increment fallback kicks in, covering the brief window where the old process hasn't fully released the socket.
|
||||
|
||||
- **Share links now respect a reverse proxy** — `_getShareBaseUrl()` rewrote every copied share link to `http://<LAN-IP>:5100` (via `/api/local_ip`), which would bypass TLS when the scanner sits behind a reverse proxy (Zoraxy, Caddy, nginx, …): a DPO opening the link would silently fall back to plain HTTP. The LAN-IP rewrite now only applies in the case it was built for — browsing the app at `localhost` over HTTP, where `window.location.origin` would produce links unusable from other machines. Any HTTPS or non-localhost origin is used as-is.
|
||||
|
||||
---
|
||||
|
||||
## [1.7.2] — 2026-06-10
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GDPR Scanner — Brugermanual
|
||||
|
||||
Version 1.7.2
|
||||
Version 1.7.4
|
||||
|
||||
---
|
||||
|
||||
@@ -671,4 +671,4 @@ For en typisk skole- eller kommunescanning er omkostningen ubetydelig — Claude
|
||||
|
||||
---
|
||||
|
||||
*GDPR Scanner v1.7.2 — teknisk opsætning og konfiguration: se README.md*
|
||||
*GDPR Scanner v1.7.4 — teknisk opsætning og konfiguration: se README.md*
|
||||
@@ -1,6 +1,6 @@
|
||||
# GDPR Scanner — User Manual
|
||||
|
||||
Version 1.7.2
|
||||
Version 1.7.4
|
||||
|
||||
---
|
||||
|
||||
@@ -671,4 +671,4 @@ For a typical school or municipality scan the cost is negligible — Claude Haik
|
||||
|
||||
---
|
||||
|
||||
*GDPR Scanner v1.7.2 — for technical setup and configuration see README.md*
|
||||
*GDPR Scanner v1.7.4 — for technical setup and configuration see README.md*
|
||||
+26
-7
@@ -2261,14 +2261,33 @@ Example --settings file with SMTP:
|
||||
|
||||
# Find a free port — auto-increment from the requested port if in use.
|
||||
import socket as _socket
|
||||
|
||||
def _can_bind(p: int, host: str) -> bool:
|
||||
with _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM) as s:
|
||||
# Probe with SO_REUSEADDR, matching how Werkzeug binds.
|
||||
# Without it, connections left in TIME_WAIT by a previous
|
||||
# instance (e.g. the in-app update restart) make the port
|
||||
# look occupied and the app silently moves to the next one.
|
||||
s.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
s.bind((host, p))
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def _find_free_port(start: int, host: str) -> int:
|
||||
for p in range(start, start + 100):
|
||||
with _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM) as s:
|
||||
try:
|
||||
s.bind((host, p))
|
||||
return p
|
||||
except OSError:
|
||||
continue
|
||||
# Give the requested port a grace period — after a self-restart
|
||||
# the previous process may not have released it yet.
|
||||
deadline = time.time() + 10
|
||||
while True:
|
||||
if _can_bind(start, host):
|
||||
return start
|
||||
if time.time() >= deadline:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
for p in range(start + 1, start + 100):
|
||||
if _can_bind(p, host):
|
||||
return p
|
||||
raise RuntimeError(f"No free port found in range {start}–{start + 99}")
|
||||
|
||||
actual_port = _find_free_port(args.port, args.host)
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ Never revert to `!!window._googleConnected` / `_fileSources.length > 0` — thos
|
||||
- **`window.VIEWER_MODE`** — injected by Jinja2. `auth.js` adds `viewer-mode` class to `<body>`; all hide rules are CSS (`body.viewer-mode …`) except `delBtn` which is also guarded in JS.
|
||||
- **`window.VIEWER_SCOPE`** — injected alongside `VIEWER_MODE`. If `VIEWER_SCOPE.role` is set, `auth.js` pre-sets `#filterRole` and hides the dropdown.
|
||||
- **Token onclick attributes** — Copy/Revoke buttons pass the token as a single-quoted JS string literal, never via `JSON.stringify` (which produces double-quoted strings that break `onclick="…"` attributes).
|
||||
- **Share link base URL** — `_getShareBaseUrl()` fetches `/api/local_ip` (LAN IP via UDP probe to `8.8.8.8`) so copied links are routable from other machines. Both `createShareLink` and `copyTokenLink` are `async`. Do not revert to `window.location.origin` — that produces `127.0.0.1` links.
|
||||
- **Share link base URL** — `_getShareBaseUrl()` uses `window.location.origin` whenever the page is served over HTTPS or from a non-localhost host (a reverse-proxied hostname or LAN IP is already routable, and rewriting it to `http://<LAN-IP>` would bypass the proxy's TLS). Only when browsing at `localhost`/`127.0.0.1` over HTTP does it fetch `/api/local_ip` (LAN IP via UDP probe to `8.8.8.8`) so copied links work from other machines. The result is cached in `_shareBaseUrl` so Copy buttons stay within the click gesture. Both `createShareLink` and `copyTokenLink` are `async`. Do not make it return bare `window.location.origin` unconditionally — that reintroduces unusable `127.0.0.1` links.
|
||||
- **Settings Security pane** — Admin PIN and Viewer PIN groups live in `stPaneSecurity`. `switchSettingsTab('security')` triggers both `stLoadPinStatus()` and `stLoadViewerPinStatus()`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
+18
-5
@@ -5,8 +5,17 @@ import { S } from './state.js';
|
||||
let _shareBaseUrl = null; // cached so Copy buttons can build the URL synchronously
|
||||
|
||||
async function _getShareBaseUrl() {
|
||||
// Use the machine's LAN IP so links work for remote users, not just localhost.
|
||||
if (_shareBaseUrl) return _shareBaseUrl;
|
||||
// The LAN-IP probe exists only to fix links when the operator browses the
|
||||
// app at localhost — those would be unusable for remote users. Any other
|
||||
// origin (LAN IP, or a reverse-proxied HTTPS hostname) is already routable,
|
||||
// and rewriting it to http://<LAN-IP> would bypass the proxy's TLS.
|
||||
const host = window.location.hostname;
|
||||
if (window.location.protocol === 'https:' ||
|
||||
(host !== 'localhost' && host !== '127.0.0.1' && host !== '[::1]')) {
|
||||
_shareBaseUrl = window.location.origin;
|
||||
return _shareBaseUrl;
|
||||
}
|
||||
try {
|
||||
const r = await fetch('/api/local_ip');
|
||||
if (r.ok) {
|
||||
@@ -129,9 +138,7 @@ function _shareScopeTypeChanged() {
|
||||
if (type === 'user') _initUserAutocomplete();
|
||||
}
|
||||
|
||||
function openShareModal() {
|
||||
document.getElementById('shareBackdrop').classList.add('open');
|
||||
document.getElementById('shareNewLinkRow').style.display = 'none';
|
||||
function _resetShareForm() {
|
||||
document.getElementById('shareLabel').value = '';
|
||||
document.getElementById('shareExpiry').value = '30';
|
||||
const scopeType = document.getElementById('shareScopeType');
|
||||
@@ -143,6 +150,12 @@ function openShareModal() {
|
||||
if (scopeDrop) scopeDrop.style.display = 'none';
|
||||
const vf = document.getElementById('shareValidFrom'); if (vf) vf.value = '';
|
||||
const vt = document.getElementById('shareValidTo'); if (vt) vt.value = '';
|
||||
}
|
||||
|
||||
function openShareModal() {
|
||||
document.getElementById('shareBackdrop').classList.add('open');
|
||||
document.getElementById('shareNewLinkRow').style.display = 'none';
|
||||
_resetShareForm();
|
||||
_renderTokenList();
|
||||
fetch('/api/viewer/pin').then(function(r){ return r.json(); }).then(function(d) {
|
||||
const el = document.getElementById('sharePinStatus');
|
||||
@@ -256,7 +269,7 @@ async function createShareLink() {
|
||||
urlInput.value = url;
|
||||
document.getElementById('shareNewLinkRow').style.display = 'block';
|
||||
document.getElementById('shareCopyBtn').textContent = t('log_copy', 'Copy');
|
||||
document.getElementById('shareLabel').value = '';
|
||||
_resetShareForm();
|
||||
_renderTokenList();
|
||||
} catch(e) {
|
||||
alert(t('share_create_error', 'Failed to create link:') + ' ' + e.message);
|
||||
|
||||
Reference in new issue
Block a user