mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2025-10-29 09:15:33 +00:00
qr -> ajax
This commit is contained in:
@@ -74,12 +74,8 @@ func (p *UserManagementAccessPage) GET(engine *templating.Engine, app core.App)
|
|||||||
data["relative_url"] = path_access + "?token=" + access_token.Token()
|
data["relative_url"] = path_access + "?token=" + access_token.Token()
|
||||||
data["validUntil"] = access_token.Expires().Time().Local().Format("02.01.2006 15:04")
|
data["validUntil"] = access_token.Expires().Time().Local().Format("02.01.2006 15:04")
|
||||||
|
|
||||||
nonce, token, err := CSRF_CACHE.GenerateTokenBundle()
|
req := templating.NewRequest(e)
|
||||||
if err != nil {
|
data["csrf_token"] = req.Session().Token
|
||||||
return engine.Response500(e, err, data)
|
|
||||||
}
|
|
||||||
data["csrf_nonce"] = nonce
|
|
||||||
data["csrf_token"] = token
|
|
||||||
|
|
||||||
SetRedirect(data, e)
|
SetRedirect(data, e)
|
||||||
|
|
||||||
@@ -94,6 +90,19 @@ func (p *UserManagementAccessPage) POST(engine *templating.Engine, app core.App)
|
|||||||
return engine.Response404(e, fmt.Errorf("invalid role: %s", role), nil)
|
return engine.Response404(e, fmt.Errorf("invalid role: %s", role), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formdata := struct {
|
||||||
|
CSRF string `json:"csrf_token" form:"csrf_token"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := e.BindBody(&formdata); err != nil {
|
||||||
|
return engine.Response500(e, fmt.Errorf("invalid form data: %w", err), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := templating.NewRequest(e)
|
||||||
|
if err := req.CheckCSRF(formdata.CSRF); err != nil {
|
||||||
|
return engine.Response500(e, fmt.Errorf("invalid CSRF token: %w", err), nil)
|
||||||
|
}
|
||||||
|
|
||||||
path_access := URL_USER_CREATE + role
|
path_access := URL_USER_CREATE + role
|
||||||
record, err := app.FindFirstRecordByData(dbmodels.ACCESS_TOKENS_TABLE, dbmodels.ACCESS_TOKENS_URL_FIELD, path_access)
|
record, err := app.FindFirstRecordByData(dbmodels.ACCESS_TOKENS_TABLE, dbmodels.ACCESS_TOKENS_URL_FIELD, path_access)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -110,6 +119,7 @@ func (p *UserManagementAccessPage) POST(engine *templating.Engine, app core.App)
|
|||||||
data["access_url"] = "https://musenalm.de" + path_access + "?token=" + token.Token()
|
data["access_url"] = "https://musenalm.de" + path_access + "?token=" + token.Token()
|
||||||
data["relative_url"] = path_access + "?token=" + token.Token()
|
data["relative_url"] = path_access + "?token=" + token.Token()
|
||||||
data["validUntil"] = token.Expires().Time().Format("02.01.2006 15:04")
|
data["validUntil"] = token.Expires().Time().Format("02.01.2006 15:04")
|
||||||
|
data["csrf_token"] = req.Session().Token
|
||||||
|
|
||||||
SetRedirect(data, e)
|
SetRedirect(data, e)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -4,10 +4,6 @@
|
|||||||
<script src="/assets/js/qrcode.min.js"></script>
|
<script src="/assets/js/qrcode.min.js"></script>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
const qrElement = document.getElementById('qr');
|
|
||||||
const tokenElement = document.getElementById('token');
|
|
||||||
const accessUrl = "{{ $model.access_url }}";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} timeout - Maximum time to wait in milliseconds.
|
* @param {number} timeout - Maximum time to wait in milliseconds.
|
||||||
* @param {number} interval - How often to check in milliseconds.
|
* @param {number} interval - How often to check in milliseconds.
|
||||||
@@ -17,15 +13,15 @@
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let elapsedTime = 0;
|
let elapsedTime = 0;
|
||||||
const checkInterval = setInterval(() => {
|
const checkInterval = setInterval(() => {
|
||||||
if (typeof window.QRCode === 'function') {
|
if (typeof window.QRCode === "function") {
|
||||||
clearInterval(checkInterval);
|
clearInterval(checkInterval);
|
||||||
resolve(window.QRCode); // Resolve with the QRCode object/function
|
resolve(window.QRCode); // Resolve with the QRCode object/function
|
||||||
} else {
|
} else {
|
||||||
elapsedTime += interval;
|
elapsedTime += interval;
|
||||||
if (elapsedTime >= timeout) {
|
if (elapsedTime >= timeout) {
|
||||||
clearInterval(checkInterval);
|
clearInterval(checkInterval);
|
||||||
console.error('Timed out waiting for QRCode to become available.');
|
console.error("Timed out waiting for QRCode to become available.");
|
||||||
reject(new Error('QRCode not available after ' + timeout + 'ms. Check if qrcode.min.js is loaded correctly and sets window.QRCode.'));
|
reject(new Error("QRCode not available after " + timeout + "ms. Check if qrcode.min.js is loaded correctly and sets window.QRCode."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, interval);
|
}, interval);
|
||||||
@@ -34,57 +30,60 @@
|
|||||||
|
|
||||||
// INFO: We have to wait for the QRCode object to be available. It's messy.
|
// INFO: We have to wait for the QRCode object to be available. It's messy.
|
||||||
async function genQRCode() {
|
async function genQRCode() {
|
||||||
|
console.debug("Generating QR Code...");
|
||||||
const QRCode = await getQRCodeWhenAvailable();
|
const QRCode = await getQRCodeWhenAvailable();
|
||||||
if (qrElement && accessUrl && qrElement.innerHTML.trim() === '') {
|
const tokenElement = document.getElementById("token");
|
||||||
|
const qrElement = document.getElementById("qr");
|
||||||
|
if (qrElement) {
|
||||||
|
// INFO: Clear previous QR code if any
|
||||||
|
// Also hide it initially to prevent flickering
|
||||||
|
qrElement.innerHTML = "";
|
||||||
|
qrElement.classList.add("hidden");
|
||||||
new QRCode(qrElement, {
|
new QRCode(qrElement, {
|
||||||
text: accessUrl,
|
text: tokenElement.value,
|
||||||
width: 1280,
|
width: 1280,
|
||||||
height: 1280,
|
height: 1280,
|
||||||
colorDark: "#000000",
|
colorDark: "#000000",
|
||||||
colorLight: "#ffffff",
|
colorLight: "#ffffff",
|
||||||
correctLevel: QRCode.CorrectLevel.H
|
correctLevel: QRCode.CorrectLevel.H,
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
qrElement.classList.remove('hidden');
|
qrElement.classList.remove("hidden");
|
||||||
}, 20);
|
}, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add event listeners to the token input field to select its content on focus or click
|
||||||
|
if (tokenElement) {
|
||||||
|
tokenElement.addEventListener("focus", (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
tokenElement.select();
|
||||||
|
});
|
||||||
|
tokenElement.addEventListener("mousedown", (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
tokenElement.select();
|
||||||
|
});
|
||||||
|
tokenElement.addEventListener("mouseup", (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
tokenElement.select();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genQRCode();
|
genQRCode();
|
||||||
if (tokenElement) {
|
window.genQRCode = genQRCode;
|
||||||
tokenElement.addEventListener('focus', (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
tokenElement.select();
|
|
||||||
});
|
|
||||||
tokenElement.addEventListener('mousedown', (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
tokenElement.select();
|
|
||||||
});
|
|
||||||
tokenElement.addEventListener('mouseup', (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
tokenElement.select();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8">
|
<div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8">
|
||||||
<div class="flex-col w-full">
|
<div class="flex-col w-full">
|
||||||
{{ if $model.redirect_url }}
|
{{ if $model.redirect_url }}
|
||||||
<a href="{{ $model.redirect_url }}" class="text-gray-700 hover:text-slate-950">
|
<a href="{{ $model.redirect_url }}" class="text-gray-700 hover:text-slate-950"> <i class="ri-arrow-left-s-line"></i> Zurück </a>
|
||||||
<i class="ri-arrow-left-s-line"></i> Zurück
|
|
||||||
</a>
|
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="/" class="text-gray-700 hover:text-slate-950">
|
<a href="/" class="text-gray-700 hover:text-slate-950"> <i class="ri-arrow-left-s-line"></i> Startseite </a>
|
||||||
<i class="ri-arrow-left-s-line"></i> Startseite
|
|
||||||
</a>
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<h1 class="text-2xl self-baseline w-full mt-6 mb-2 font-bold text-slate-900">
|
<h1 class="text-2xl self-baseline w-full mt-6 mb-2 font-bold text-slate-900">Nutzer einladen</h1>
|
||||||
Nutzer einladen
|
|
||||||
</h1>
|
|
||||||
<div class="text-sm text-slate-600 !hyphens-auto mb-6 max-w-[60ch]">
|
<div class="text-sm text-slate-600 !hyphens-auto mb-6 max-w-[60ch]">
|
||||||
<i class="ri-question-line"></i>
|
<i class="ri-question-line"></i>
|
||||||
Mit diesem Link können neue Accounts registriert werden. Geben Sie den Link an Personen
|
Mit diesem Link können neue Accounts registriert werden. Geben Sie den Link an Personen weiter, die Sie einladen möchten.
|
||||||
weiter, die Sie einladen möchten.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,10 +92,12 @@
|
|||||||
<div class="flex flex-row w-full">
|
<div class="flex flex-row w-full">
|
||||||
<div class="flex-col max-w-xl w-full">
|
<div class="flex-col max-w-xl w-full">
|
||||||
<!-- QR Code -->
|
<!-- QR Code -->
|
||||||
<div class="mb-4 p-4 border rounded-xs hidden" id="qr"></div>
|
<div class="mb-4 p-4 border rounded-xs min-w-full aspect-square">
|
||||||
|
<div id="qr"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Access URL and Token -->
|
<!-- Access URL and Token -->
|
||||||
<div class="mb-6 flex flex-col">
|
<div class="mb-6 flex flex-col" id="token-container">
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -116,13 +117,14 @@
|
|||||||
shadow-sm text-sm font-medium text-white bg-slate-700 hover:bg-slate-800 cursor-pointer
|
shadow-sm text-sm font-medium text-white bg-slate-700 hover:bg-slate-800 cursor-pointer
|
||||||
focus:outline-none no-underline
|
focus:outline-none no-underline
|
||||||
focus:ring-2 focus:ring-offset-2 focus:ring-slate-500"
|
focus:ring-2 focus:ring-offset-2 focus:ring-slate-500"
|
||||||
onclick="navigator.clipboard.writeText('{{ $model.access_url }}')">
|
onclick="navigator.clipboard.writeText(token.value)">
|
||||||
<i class="ri-file-copy-line"></i>
|
<i class="ri-file-copy-line"></i>
|
||||||
</button>
|
</button>
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
<tool-tip position="top">
|
<tool-tip position="top">
|
||||||
<div class="data-tip font-bold hidden">In neuem Fenster öffnen</div>
|
<div class="data-tip font-bold hidden">In neuem Fenster öffnen</div>
|
||||||
<a
|
<a
|
||||||
|
id="access-link"
|
||||||
href="{{ $model.relative_url }}"
|
href="{{ $model.relative_url }}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="ml-2 inline-flex justify-center py-2 px-3 border border-transparent
|
class="ml-2 inline-flex justify-center py-2 px-3 border border-transparent
|
||||||
@@ -133,19 +135,8 @@
|
|||||||
<i class="ri-external-link-line"></i>
|
<i class="ri-external-link-line"></i>
|
||||||
</a>
|
</a>
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
<form class="" method="POST">
|
<form class="" method="POST" hx-boost="false" x-target="token access-link" @ajax:after="genQRCode()">
|
||||||
<input
|
<input type="hidden" name="csrf_token" id="csrf_token" required value="{{ $model.csrf_token }}" />
|
||||||
type="hidden"
|
|
||||||
name="csrf_nonce"
|
|
||||||
id="csrf_nonce"
|
|
||||||
required
|
|
||||||
value="{{ $model.csrf_nonce }}" />
|
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="csrf_token"
|
|
||||||
id="csrf_token"
|
|
||||||
required
|
|
||||||
value="{{ $model.csrf_token }}" />
|
|
||||||
<div class="col-span-9 flex flex-row items-center justify-center">
|
<div class="col-span-9 flex flex-row items-center justify-center">
|
||||||
<tool-tip position="top">
|
<tool-tip position="top">
|
||||||
<div class="data-tip font-bold hidden">Link invalidieren</div>
|
<div class="data-tip font-bold hidden">Link invalidieren</div>
|
||||||
@@ -175,52 +166,31 @@
|
|||||||
<div class="ml-12 flex flex-col">
|
<div class="ml-12 flex flex-col">
|
||||||
<!-- User Role Chooser -->
|
<!-- User Role Chooser -->
|
||||||
<div class="user-chooser flex flex-col gap-y-3">
|
<div class="user-chooser flex flex-col gap-y-3">
|
||||||
<a
|
<a href="/user/management/access/User?redirectTo={{ $model.redirect_url }}" {{ if eq $model.role "User" }}aria-current="page"{{ end }}> Benutzer </a>
|
||||||
href="/user/management/access/User?redirectTo={{ $model.redirect_url }}"
|
|
||||||
{{ if eq $model.role "User" }}aria-current="page"{{ end }}>
|
|
||||||
Benutzer
|
|
||||||
</a>
|
|
||||||
{{ if eq $model.role "User" }}
|
{{ if eq $model.role "User" }}
|
||||||
<div class="max-w-[60ch] text-sm text-blue-600 flex flex-row gap-x-2 hyphens-auto">
|
<div class="max-w-[60ch] text-sm text-blue-600 flex flex-row gap-x-2 hyphens-auto">
|
||||||
<div>
|
<div>
|
||||||
<i class="ri-error-warning-line"></i>
|
<i class="ri-error-warning-line"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>Benutzer können private Felder und Datensätze einsehen, aber nicht bearbeiten oder löschen.</div>
|
||||||
Benutzer können private Felder und Datensätze einsehen, aber nicht bearbeiten oder
|
|
||||||
löschen.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
<a
|
<a href="/user/management/access/Editor?redirectTo={{ $model.redirect_url }}" {{ if eq $model.role "Editor" }}aria-current="page"{{ end }}> Redakteur </a>
|
||||||
href="/user/management/access/Editor?redirectTo={{ $model.redirect_url }}"
|
|
||||||
{{ if eq $model.role "Editor" }}aria-current="page"{{ end }}>
|
|
||||||
Redakteur
|
|
||||||
</a>
|
|
||||||
{{- if eq $model.role "Editor" -}}
|
{{- if eq $model.role "Editor" -}}
|
||||||
<div class="max-w-[60ch] text-sm text-orange-600 flex flex-row gap-x-2 hyphens-auto">
|
<div class="max-w-[60ch] text-sm text-orange-600 flex flex-row gap-x-2 hyphens-auto">
|
||||||
<div>
|
<div>
|
||||||
<i class="ri-error-warning-line"></i>
|
<i class="ri-error-warning-line"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>Redakteure können alle Felder und Datensätze der Datenbank einsehen, bearbeiten oder löschen.</div>
|
||||||
Redakteure können alle Felder und Datensätze der Datenbank einsehen, bearbeiten oder
|
|
||||||
löschen.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
<a
|
<a href="/user/management/access/Admin?redirectTo={{ $model.redirect_url }}" {{ if eq $model.role "Admin" }}aria-current="page"{{ end }}> Admin </a>
|
||||||
href="/user/management/access/Admin?redirectTo={{ $model.redirect_url }}"
|
|
||||||
{{ if eq $model.role "Admin" }}aria-current="page"{{ end }}>
|
|
||||||
Admin
|
|
||||||
</a>
|
|
||||||
{{- if eq $model.role "Admin" -}}
|
{{- if eq $model.role "Admin" -}}
|
||||||
<div class="max-w-[60ch] text-sm text-red-600 flex flex-row gap-x-2 hyphens-auto">
|
<div class="max-w-[60ch] text-sm text-red-600 flex flex-row gap-x-2 hyphens-auto">
|
||||||
<div>
|
<div>
|
||||||
<i class="ri-error-warning-line"></i>
|
<i class="ri-error-warning-line"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>Administratoren können alle Felder und Datensätze einsehen, bearbeiten oder löschen. Administratoren können Passwörter setzen, Nutzer einladen und deaktivieren.</div>
|
||||||
Administratoren können alle Felder und Datensätze einsehen, bearbeiten oder löschen.
|
|
||||||
Administratoren können Passwörter setzen, Nutzer einladen und deaktivieren.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user