feat: role-scoped viewer tokens — restrict shared links to student or staff items

Add a Role scope dropdown to the Share modal (All roles / Ansatte / Elever).
Scope is stored as {"role": "student"|"staff"} in viewer_tokens.json and
enforced server-side in GET /api/db/flagged via session["viewer_scope"].
Client-side, #filterRole is pre-set and hidden for scoped viewers so the
constraint cannot be bypassed. Existing tokens and PIN sessions remain
unrestricted. Role badge shown on each scoped token row in the Active links list.

Files: app_config.py, routes/viewer.py, routes/database.py, gdpr_scanner.py,
templates/index.html, static/js/viewer.js, static/js/auth.js,
lang/en.json, lang/da.json, lang/de.json,
CLAUDE.md, CHANGELOG.md, README.md, MANUAL-EN.md, MANUAL-DA.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
StyxX65andClaude Sonnet 4.6 committed 2026-04-12 09:30:38 +02:00
1 parent 0c35a7a83d
commit 1aaf400771
15 files changed
+99 -26

No files matched your search

+10 -1
View File
@@ -12,7 +12,8 @@
// ── i18n ─────────────────────────────────────────────────────────────────────
var LANG = {{ lang_json | safe }};
// ── Viewer mode ───────────────────────────────────────────────────────────────
window.VIEWER_MODE = {{ 'true' if viewer_mode else 'false' }};
window.VIEWER_MODE = {{ 'true' if viewer_mode else 'false' }};
window.VIEWER_SCOPE = {{ viewer_scope | safe if viewer_scope is defined else '{}' }};
function t(key, fallback) {
return LANG[key] !== undefined ? LANG[key] : (fallback !== undefined ? fallback : key);
}
@@ -881,6 +882,14 @@ document.addEventListener('DOMContentLoaded', applyI18n);
<div style="font-size:11px;color:var(--muted);margin-bottom:3px" data-i18n="share_label_lbl">Label (optional)</div>
<input id="shareLabel" type="text" data-i18n-placeholder="share_label_placeholder" placeholder="e.g. DPO review 2026" style="width:100%;box-sizing:border-box;font-size:12px;padding:5px 8px;background:var(--surface);border:1px solid var(--border);border-radius:5px;color:var(--text)">
</div>
<div style="width:100px">
<div style="font-size:11px;color:var(--muted);margin-bottom:3px" data-i18n="share_scope_lbl">Role scope</div>
<select id="shareScope" style="width:100%;font-size:12px;padding:5px 6px;background:var(--surface);border:1px solid var(--border);border-radius:5px;color:var(--text)">
<option value="" data-i18n="share_scope_all">All roles</option>
<option value="staff" data-i18n="share_scope_staff">Ansatte</option>
<option value="student" data-i18n="share_scope_student">Elever</option>
</select>
</div>
<div style="width:100px">
<div style="font-size:11px;color:var(--muted);margin-bottom:3px" data-i18n="share_expires_in">Expires in</div>
<select id="shareExpiry" style="width:100%;font-size:12px;padding:5px 6px;background:var(--surface);border:1px solid var(--border);border-radius:5px;color:var(--text)">