mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2025-10-28 16:55:32 +00:00
nutzer einladen + sesssion cache correct clear
This commit is contained in:
@@ -99,6 +99,28 @@ func (c *UserSessionCache) Delete(sessionTokenClear string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UserSessionCache) DeleteSessionByUserID(uid string) {
|
||||
if uid == "" {
|
||||
return
|
||||
}
|
||||
|
||||
c.cache.Range(func(key, value any) bool {
|
||||
entry, ok := value.(*cacheEntry)
|
||||
if !ok {
|
||||
c.cache.Delete(key)
|
||||
return true
|
||||
}
|
||||
|
||||
if entry.user.Id == uid {
|
||||
c.cache.Delete(key)
|
||||
c.mu.Lock()
|
||||
c.approximateSize--
|
||||
c.mu.Unlock()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (c *UserSessionCache) Clear() {
|
||||
c.cache.Clear()
|
||||
c.mu.Lock()
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/middleware"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tools/router"
|
||||
)
|
||||
@@ -76,6 +77,32 @@ func (p *UserEditPage) GET(engine *templating.Engine, app core.App) HandleFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteSessionsForUser(app core.App, uid string) error {
|
||||
defer middleware.SESSION_CACHE.DeleteSessionByUserID(uid)
|
||||
records := []*core.Record{}
|
||||
err := app.RecordQuery(dbmodels.SESSIONS_TABLE).
|
||||
Where(dbx.HashExp{dbmodels.SESSIONS_USER_FIELD: uid}).
|
||||
All(&records)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = app.RunInTransaction(func(tx core.App) error {
|
||||
for _, r := range records {
|
||||
if err := tx.Delete(r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func InvalidDataResponse(engine *templating.Engine, e *core.RequestEvent, error string, user *dbmodels.FixedUser) error {
|
||||
data := make(map[string]any)
|
||||
data["error"] = error
|
||||
@@ -153,10 +180,12 @@ func (p *UserEditPage) POST(engine *templating.Engine, app core.App) HandleFunc
|
||||
user_proxy.SetEmail(formdata.Email)
|
||||
user_proxy.SetName(formdata.Name)
|
||||
|
||||
rolechanged := false
|
||||
if formdata.Role != "" && formdata.Role != user_proxy.Role() {
|
||||
if user.Role == "Admin" &&
|
||||
(formdata.Role == "User" || formdata.Role == "Editor" || formdata.Role == "Admin") {
|
||||
user_proxy.SetRole(formdata.Role)
|
||||
rolechanged = true
|
||||
} else {
|
||||
return InvalidDataResponse(engine, e, "Rolle nicht erlaubt", &fu)
|
||||
}
|
||||
@@ -180,8 +209,16 @@ func (p *UserEditPage) POST(engine *templating.Engine, app core.App) HandleFunc
|
||||
return InvalidDataResponse(engine, e, err.Error(), &fu)
|
||||
}
|
||||
|
||||
// TODO: this is lazy, we just need to delete the sessions of the changed user
|
||||
middleware.SESSION_CACHE.Clear()
|
||||
if rolechanged {
|
||||
if err := DeleteSessionsForUser(app, user_proxy.Id); err != nil {
|
||||
return InvalidDataResponse(engine, e, "Fehler beim Löschen der Sitzungen: "+err.Error(), &fu)
|
||||
}
|
||||
|
||||
if user_proxy.Id == user.Id {
|
||||
// INFO: user changed his own role, so we log him out
|
||||
return e.Redirect(303, "/login/")
|
||||
}
|
||||
}
|
||||
|
||||
fu = user_proxy.Fixed()
|
||||
data["user"] = &fu
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -33,7 +33,7 @@
|
||||
{{ end }}
|
||||
<form class="w-full grid grid-cols-3 gap-4" method="POST" x-data="{ openpw: false }">
|
||||
<div
|
||||
class="col-span-3 border-2 border-transparent px-2
|
||||
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||
<label for="username" class="text-sm text-gray-700 font-bold">
|
||||
@@ -43,7 +43,7 @@
|
||||
type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
class="mt-1 block w-full rounded-md focus:border-none focus:outline-none"
|
||||
class="mt-1 block w-full focus:border-none focus:outline-none"
|
||||
placeholder=""
|
||||
required
|
||||
autocomplete="off"
|
||||
@@ -51,7 +51,7 @@
|
||||
autofocus />
|
||||
</div>
|
||||
<div
|
||||
class="col-span-3 border-2 border-transparent px-2
|
||||
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||
<label for="username" class="text-sm text-gray-700 font-bold">
|
||||
@@ -68,7 +68,7 @@
|
||||
value="{{ $model.user.Email }}" />
|
||||
</div>
|
||||
<div
|
||||
class="col-span-3 border-2 border-transparent px-2
|
||||
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||
<label for="role" class="text-sm text-gray-700 font-bold">
|
||||
@@ -94,6 +94,32 @@
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
{{- if and
|
||||
(eq $model.request.user.Role "Admin")
|
||||
(eq $model.request.user.Id
|
||||
$model.user.Id)
|
||||
-}}
|
||||
<div class="flex flex-row col-span-3">
|
||||
<div class="align-top">
|
||||
<i class="ri-information-line text-gray-700 mt-2 mr-2 align-top"></i>
|
||||
</div>
|
||||
<p class="text-sm text-gray-700 max-w-[80ch]">
|
||||
Achtung! Wenn Sie Ihre eigenen Berechtigungen ändern, verlieren Sie die Möglichkeit,
|
||||
diese Änderungen rückgängig zu machen. Weiter werden Sie von allen laufenden Sitzungen
|
||||
abgemeldet und müssen sich erneut anmelden.
|
||||
</p>
|
||||
</div>
|
||||
{{- else if (eq $model.request.user.Role "Admin") -}}
|
||||
<div class="flex flex-row col-span-3">
|
||||
<div class="align-top">
|
||||
<i class="ri-information-line text-gray-700 mt-2 mr-2 align-top"></i>
|
||||
</div>
|
||||
<p class="text-sm text-gray-700 max-w-[80ch]">
|
||||
Achtung! Wenn Sie die Rolle eines Benutzers ändern, wird dieser von allen laufenden
|
||||
Sitzungen abgemeldet und muss sich erneut anmelden.
|
||||
</p>
|
||||
</div>
|
||||
{{- end -}}
|
||||
<div class="col-span-3">
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="openpw" id="openpw" x-model="openpw" class="mr-2" />
|
||||
@@ -105,7 +131,7 @@
|
||||
{{- if not (eq $model.request.user.Role "Admin") -}}
|
||||
<div
|
||||
x-bind:style="!openpw ? 'display:none' : ''"
|
||||
class="col-span-3 border-2 border-transparent px-2
|
||||
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||
<label for="password_old" class="text-sm text-gray-700 font-bold"> Altes Passwort </label>
|
||||
@@ -120,7 +146,7 @@
|
||||
{{- end -}}
|
||||
<div
|
||||
x-bind:style="!openpw ? 'display:none' : ''"
|
||||
class="col-span-3 border-2 border-transparent px-2
|
||||
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||
<label for="password" class="text-sm text-gray-700 font-bold"> Neues Passwort </label>
|
||||
@@ -135,7 +161,7 @@
|
||||
</div>
|
||||
<div
|
||||
x-bind:style="!openpw ? 'display:none' : ''"
|
||||
class="col-span-3 border-2 border-transparent px-2
|
||||
class="rounded-xs col-span-3 border-2 border-transparent px-3
|
||||
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
|
||||
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
|
||||
<label for="password_repeat" class="text-sm text-gray-700 font-bold">
|
||||
|
||||
@@ -88,131 +88,135 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-invites flex container-normal mx-auto px-8 mt-4">
|
||||
<div class="flex-col max-w-xl w-full">
|
||||
<div
|
||||
class="col-span-9 grid grid-cols-3 justify-between mb-4 py-1 px-1 border rounded-xs gap-x-2
|
||||
bg-slate-200 user-chooser">
|
||||
<a
|
||||
href="/user/management/access/User?redirectTo={{ $model.redirect_url }}"
|
||||
class="text-center px-4 text-gray-700 hover:text-slate-950 block"
|
||||
{{ if eq $model.role "User" }}aria-current="page"{{ end }}>
|
||||
Benutzer
|
||||
</a>
|
||||
<a
|
||||
href="/user/management/access/Editor?redirectTo={{ $model.redirect_url }}"
|
||||
class="text-gray-700 hover:text-slate-950 block px-4 text-center"
|
||||
{{ if eq $model.role "Editor" }}aria-current="page"{{ end }}>
|
||||
Redakteur
|
||||
</a>
|
||||
<a
|
||||
href="/user/management/access/Admin?redirectTo={{ $model.redirect_url }}"
|
||||
class="text-gray-700 hover:text-slate-950 text-center px-4 block"
|
||||
{{ if eq $model.role "Admin" }}aria-current="page"{{ end }}>
|
||||
Admin
|
||||
</a>
|
||||
</div>
|
||||
<!--
|
||||
<div class="flex justify-center mt-8 items-baseline">
|
||||
<div>
|
||||
<img class="h-20 w-20 border" src="/assets/favicon.png" />
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
<div class="col-span-7 col-start-2 mb-4 p-4 border rounded-xs hidden" id="qr"></div>
|
||||
<div class="col-span-9 mb-6 flex flex-col">
|
||||
<div class="flex flex-row">
|
||||
<input
|
||||
type="text"
|
||||
name="token"
|
||||
id="token"
|
||||
class="w-full text-center border border-slate-300 rounded-xs
|
||||
<div class="flex flex-row w-full">
|
||||
<div class="flex-col max-w-xl w-full">
|
||||
<!-- QR Code -->
|
||||
<div class="mb-4 p-4 border rounded-xs hidden" id="qr"></div>
|
||||
|
||||
<!-- Access URL and Token -->
|
||||
<div class="mb-6 flex flex-col">
|
||||
<div class="flex flex-row">
|
||||
<input
|
||||
type="text"
|
||||
name="token"
|
||||
id="token"
|
||||
class="w-full text-center border border-slate-300 rounded-xs
|
||||
focus:border-slate-500 focus:ring-slate-500 p-1 px-2 overflow-ellipsis"
|
||||
value="{{ $model.access_url }}"
|
||||
deactive
|
||||
readonly />
|
||||
<button
|
||||
type="button"
|
||||
class="ml-2 inline-flex justify-center py-2 px-3 border border-transparent
|
||||
value="{{ $model.access_url }}"
|
||||
deactive
|
||||
readonly />
|
||||
<button
|
||||
type="button"
|
||||
class="ml-2 inline-flex justify-center py-2 px-3 border border-transparent
|
||||
rounded-xs
|
||||
shadow-sm text-sm font-medium text-white bg-slate-700 hover:bg-slate-800 cursor-pointer
|
||||
focus:outline-none no-underline
|
||||
focus:ring-2 focus:ring-offset-2 focus:ring-slate-500"
|
||||
onclick="navigator.clipboard.writeText('{{ $model.access_url }}')">
|
||||
<i class="ri-file-copy-line"></i>
|
||||
</button>
|
||||
<a
|
||||
href="{{ $model.relative_url }}"
|
||||
target="_blank"
|
||||
class="ml-2 inline-flex justify-center py-2 px-3 border border-transparent
|
||||
onclick="navigator.clipboard.writeText('{{ $model.access_url }}')">
|
||||
<i class="ri-file-copy-line"></i>
|
||||
</button>
|
||||
<a
|
||||
href="{{ $model.relative_url }}"
|
||||
target="_blank"
|
||||
class="ml-2 inline-flex justify-center py-2 px-3 border border-transparent
|
||||
rounded-xs
|
||||
shadow-sm text-sm font-medium text-white bg-slate-700 hover:bg-slate-800 cursor-pointer
|
||||
focus:outline-none no-underline
|
||||
focus:ring-2 focus:ring-offset-2 focus:ring-slate-500">
|
||||
<i class="ri-external-link-line"></i>
|
||||
</a>
|
||||
<form class="" method="POST">
|
||||
<input
|
||||
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">
|
||||
<button
|
||||
type="submit"
|
||||
class="ml-2 inline-flex justify-center py-2 px-3 border border-transparent
|
||||
<i class="ri-external-link-line"></i>
|
||||
</a>
|
||||
<form class="" method="POST">
|
||||
<input
|
||||
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">
|
||||
<button
|
||||
type="submit"
|
||||
class="ml-2 inline-flex justify-center py-2 px-3 border border-transparent
|
||||
rounded-xs
|
||||
shadow-sm text-sm font-medium text-white bg-slate-700 hover:bg-slate-800 cursor-pointer
|
||||
focus:outline-none no-underline
|
||||
focus:ring-2 focus:ring-offset-2 focus:ring-slate-500">
|
||||
<i class="ri-loop-left-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<i class="ri-loop-left-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="text-right text-slate-400 mb-1 mt-3">
|
||||
Gültig bis
|
||||
{{ $model.validUntil }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-9 text-right text-slate-400 mb-1 mt-3">
|
||||
Gültig bis
|
||||
{{ $model.validUntil }}
|
||||
</div>
|
||||
{{ if eq $model.role "User" }}
|
||||
<div class="max-w-[60ch] mt-1 text-sm text-blue-600 flex flex-row gap-x-2 hyphens-auto">
|
||||
<div>
|
||||
<i class="ri-error-warning-line"></i>
|
||||
</div>
|
||||
<div>
|
||||
Benutzer können private Felder und Datensätze einsehen, aber nicht bearbeiten oder
|
||||
löschen.
|
||||
</div>
|
||||
</div>
|
||||
{{ else if eq $model.role "Admin" }}
|
||||
<div class="max-w-[60ch] mt-1 text-sm text-red-600 flex flex-row gap-x-2 hyphens-auto">
|
||||
<div>
|
||||
<i class="ri-error-warning-line"></i>
|
||||
</div>
|
||||
<div>
|
||||
Administratoren können alle Felder und Datensätze einsehen, bearbeiten und löschen.
|
||||
Administratoren können Passwörter setzen, Nutzer einladen und deaktivieren.
|
||||
</div>
|
||||
</div>
|
||||
{{ else if eq $model.role "Editor" }}
|
||||
<div class="max-w-[60ch] mt-1 text-sm text-orange-600 flex flex-row gap-x-2 hyphens-auto">
|
||||
<div>
|
||||
<i class="ri-error-warning-line"></i>
|
||||
</div>
|
||||
<div>
|
||||
Redakteure können alle Felder und Datensätze der Datenbank einsehen, bearbeiten und
|
||||
löschen.
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
<!-- End of Access URL and Token -->
|
||||
</div>
|
||||
<!-- End of First Column -->
|
||||
|
||||
<!-- Second column -->
|
||||
<div class="ml-12 flex flex-col">
|
||||
<!-- User Role Chooser -->
|
||||
<div class="user-chooser flex flex-col gap-y-3">
|
||||
<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" }}
|
||||
<div class="max-w-[60ch] text-sm text-blue-600 flex flex-row gap-x-2 hyphens-auto">
|
||||
<div>
|
||||
<i class="ri-error-warning-line"></i>
|
||||
</div>
|
||||
<div>
|
||||
Benutzer können private Felder und Datensätze einsehen, aber nicht bearbeiten oder
|
||||
löschen.
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
<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" -}}
|
||||
<div class="max-w-[60ch] text-sm text-orange-600 flex flex-row gap-x-2 hyphens-auto">
|
||||
<div>
|
||||
<i class="ri-error-warning-line"></i>
|
||||
</div>
|
||||
<div>
|
||||
Redakteure können alle Felder und Datensätze der Datenbank einsehen, bearbeiten und
|
||||
löschen.
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
<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" -}}
|
||||
<div class="max-w-[60ch] text-sm text-red-600 flex flex-row gap-x-2 hyphens-auto">
|
||||
<div>
|
||||
<i class="ri-error-warning-line"></i>
|
||||
</div>
|
||||
<div>
|
||||
Administratoren können alle Felder und Datensätze einsehen, bearbeiten und löschen.
|
||||
Administratoren können Passwörter setzen, Nutzer einladen und deaktivieren.
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
<!-- End of User Role Chooser -->
|
||||
</div>
|
||||
<!-- End of second column -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<title>Musenalm – Nutzer einladen</title>
|
||||
|
||||
@@ -512,12 +512,24 @@
|
||||
@apply !text-slate-900 bg-stone-50;
|
||||
}
|
||||
|
||||
.user-invites .user-chooser a {
|
||||
@apply py-1 rounded-xs no-underline;
|
||||
.user-chooser a {
|
||||
@apply px-4 py-2 no-underline text-gray-500 hover:text-slate-900 font-serif font-bold border-l-4 border-transparent hover:bg-slate-100 transition-all duration-75 rounded-xs;
|
||||
}
|
||||
|
||||
.user-invites .user-chooser a[aria-current="page"] {
|
||||
@apply font-bold !bg-stone-50 relative border-b z-20 shadow;
|
||||
.user-chooser a[aria-current="page"] {
|
||||
@apply text-slate-900 font-bold bg-slate-100 border-slate-900 shadow-sm;
|
||||
}
|
||||
|
||||
.user-chooser a[aria-current="page"]:nth-child(1) {
|
||||
@apply border-blue-500;
|
||||
}
|
||||
|
||||
.user-chooser a[aria-current="page"]:nth-child(2) {
|
||||
@apply border-orange-600;
|
||||
}
|
||||
|
||||
.user-chooser a[aria-current="page"]:nth-child(3) {
|
||||
@apply border-red-600;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
||||
Reference in New Issue
Block a user