feat: allow multiple auth sources per user (#500,#477) (#612)

* feat: allow multiple auth sources per user (#500,#477)

* only override isAdmin flag if it is provided by the authentication source
This commit is contained in:
h44z
2026-01-21 22:22:22 +01:00
committed by GitHub
parent d2fe267be7
commit e0f6c1d04b
44 changed files with 1158 additions and 798 deletions

View File

@@ -42,7 +42,7 @@ const passwordWeak = computed(() => {
})
const formValid = computed(() => {
if (formData.value.Source !== 'db') {
if (!formData.value.AuthSources.some(s => s === 'db')) {
return true // nothing to validate
}
if (props.userId !== '#NEW#' && passwordWeak.value) {
@@ -70,7 +70,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
} else { // fill existing userdata
formData.value.Identifier = selectedUser.value.Identifier
formData.value.Email = selectedUser.value.Email
formData.value.Source = selectedUser.value.Source
formData.value.AuthSources = selectedUser.value.AuthSources
formData.value.IsAdmin = selectedUser.value.IsAdmin
formData.value.Firstname = selectedUser.value.Firstname
formData.value.Lastname = selectedUser.value.Lastname
@@ -80,6 +80,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
formData.value.Password = ""
formData.value.Disabled = selectedUser.value.Disabled
formData.value.Locked = selectedUser.value.Locked
formData.value.PersistLocalChanges = selectedUser.value.PersistLocalChanges
}
}
}
@@ -133,7 +134,7 @@ async function del() {
<template>
<Modal :title="title" :visible="visible" @close="close">
<template #default>
<fieldset v-if="formData.Source==='db'">
<fieldset>
<legend class="mt-4">{{ $t('modals.user-edit.header-general') }}</legend>
<div v-if="props.userId==='#NEW#'" class="form-group">
<label class="form-label mt-4">{{ $t('modals.user-edit.identifier.label') }}</label>
@@ -141,16 +142,22 @@ async function del() {
</div>
<div class="form-group">
<label class="form-label mt-4">{{ $t('modals.user-edit.source.label') }}</label>
<input v-model="formData.Source" class="form-control" disabled="disabled" :placeholder="$t('modals.user-edit.source.placeholder')" type="text">
<input v-model="formData.AuthSources" class="form-control" disabled="disabled" :placeholder="$t('modals.user-edit.source.placeholder')" type="text">
</div>
<div v-if="formData.Source==='db'" class="form-group">
<div class="form-group" v-if="formData.AuthSources.some(s => s ==='db')">
<label class="form-label mt-4">{{ $t('modals.user-edit.password.label') }}</label>
<input v-model="formData.Password" aria-describedby="passwordHelp" class="form-control" :class="{ 'is-invalid': passwordWeak, 'is-valid': formData.Password !== '' && !passwordWeak }" :placeholder="$t('modals.user-edit.password.placeholder')" type="password">
<div class="invalid-feedback">{{ $t('modals.user-edit.password.too-weak') }}</div>
<small v-if="props.userId!=='#NEW#'" id="passwordHelp" class="form-text text-muted">{{ $t('modals.user-edit.password.description') }}</small>
</div>
</fieldset>
<fieldset v-if="formData.Source==='db'">
<fieldset v-if="formData.AuthSources.some(s => s !=='db') && !formData.PersistLocalChanges">
<legend class="mt-4">{{ $t('modals.user-edit.header-personal') }}</legend>
<div class="alert alert-warning mt-3">
{{ $t('modals.user-edit.sync-warning') }}
</div>
</fieldset>
<fieldset v-if="!formData.AuthSources.some(s => s !=='db') || formData.PersistLocalChanges">
<legend class="mt-4">{{ $t('modals.user-edit.header-personal') }}</legend>
<div class="form-group">
<label class="form-label mt-4">{{ $t('modals.user-edit.email.label') }}</label>
@@ -194,10 +201,14 @@ async function del() {
<input v-model="formData.Locked" class="form-check-input" type="checkbox">
<label class="form-check-label" >{{ $t('modals.user-edit.locked.label') }}</label>
</div>
<div class="form-check form-switch" v-if="formData.Source==='db'">
<div class="form-check form-switch" v-if="!formData.AuthSources.some(s => s !=='db') || formData.PersistLocalChanges">
<input v-model="formData.IsAdmin" checked="" class="form-check-input" type="checkbox">
<label class="form-check-label">{{ $t('modals.user-edit.admin.label') }}</label>
</div>
<div class="form-check form-switch" v-if="formData.AuthSources.some(s => s !=='db')">
<input v-model="formData.PersistLocalChanges" class="form-check-input" type="checkbox">
<label class="form-check-label" >{{ $t('modals.user-edit.persist-local-changes.label') }}</label>
</div>
</fieldset>
</template>

View File

@@ -137,7 +137,7 @@ export function freshUser() {
Identifier: "",
Email: "",
Source: "db",
AuthSources: ["db"],
IsAdmin: false,
Firstname: "",
@@ -155,6 +155,8 @@ export function freshUser() {
ApiEnabled: false,
PersistLocalChanges: false,
PeerCount: 0,
// Internal values

View File

@@ -147,7 +147,7 @@
"email": "E-Mail",
"firstname": "Vorname",
"lastname": "Nachname",
"source": "Quelle",
"sources": "Quellen",
"peers": "Peers",
"admin": "Admin"
},
@@ -378,7 +378,11 @@
},
"admin": {
"label": "Ist Administrator"
}
},
"persist-local-changes": {
"label": "Lokale Änderungen speichern"
},
"sync-warning": "Um diesen synchronisierten Benutzer zu bearbeiten, aktivieren Sie die lokale Änderungsspeicherung. Andernfalls werden Ihre Änderungen bei der nächsten Synchronisierung überschrieben."
},
"interface-view": {
"headline": "Konfiguration für Schnittstelle:"

View File

@@ -147,7 +147,7 @@
"email": "E-Mail",
"firstname": "Firstname",
"lastname": "Lastname",
"source": "Source",
"sources": "Sources",
"peers": "Peers",
"admin": "Admin"
},
@@ -378,7 +378,11 @@
},
"admin": {
"label": "Is Admin"
}
},
"persist-local-changes": {
"label": "Persist local changes"
},
"sync-warning": "To modify this synchronized user, enable local change persistence. Otherwise, your changes will be overwritten during the next synchronization."
},
"interface-view": {
"headline": "Config for Interface:"

View File

@@ -162,7 +162,7 @@
"email": "Correo electrónico",
"firstname": "Nombre",
"lastname": "Apellido",
"source": "Origen",
"sources": "Origen",
"peers": "Peers",
"admin": "Administrador"
},

View File

@@ -137,7 +137,7 @@
"email": "E-mail",
"firstname": "Prénom",
"lastname": "Nom",
"source": "Source",
"sources": "Sources",
"peers": "Pairs",
"admin": "Admin"
},

View File

@@ -136,7 +136,7 @@
"email": "이메일",
"firstname": "이름",
"lastname": "성",
"source": "소스",
"sources": "소스",
"peers": "피어",
"admin": "관리자"
},

View File

@@ -137,7 +137,7 @@
"email": "E-Mail",
"firstname": "Primeiro Nome",
"lastname": "Último Nome",
"source": "Fonte",
"sources": "Fonte",
"peers": "Peers",
"admin": "Administrador"
},

View File

@@ -143,7 +143,7 @@
"email": "Электронная почта",
"firstname": "Имя",
"lastname": "Фамилия",
"source": "Источник",
"sources": "Источник",
"peers": "Пиры",
"admin": "Админ"
},

View File

@@ -135,7 +135,7 @@
"email": "E-Mail",
"firstname": "Ім'я",
"lastname": "Прізвище",
"source": "Джерело",
"sources": "Джерело",
"peers": "Піри",
"admin": "Адміністратор"
},

View File

@@ -134,7 +134,7 @@
"email": "E-Mail",
"firstname": "Tên",
"lastname": "Họ",
"source": "Nguồn",
"sources": "Nguồn",
"peers": "Peers",
"admin": "Quản trị viên"
},

View File

@@ -134,7 +134,7 @@
"email": "电子邮件",
"firstname": "名",
"lastname": "姓",
"source": "来源",
"sources": "来源",
"peers": "节点",
"admin": "管理员"
},

View File

@@ -131,7 +131,7 @@ onMounted(() => {
<th scope="col">{{ $t('users.table-heading.email') }}</th>
<th scope="col">{{ $t('users.table-heading.firstname') }}</th>
<th scope="col">{{ $t('users.table-heading.lastname') }}</th>
<th class="text-center" scope="col">{{ $t('users.table-heading.source') }}</th>
<th class="text-center" scope="col">{{ $t('users.table-heading.sources') }}</th>
<th class="text-center" scope="col">{{ $t('users.table-heading.peers') }}</th>
<th class="text-center" scope="col">{{ $t('users.table-heading.admin') }}</th>
<th scope="col"></th><!-- Actions -->
@@ -150,7 +150,7 @@ onMounted(() => {
<td>{{user.Email}}</td>
<td>{{user.Firstname}}</td>
<td>{{user.Lastname}}</td>
<td class="text-center"><span class="badge rounded-pill bg-light">{{user.Source}}</span></td>
<td><span class="badge bg-light me-1" v-for="src in user.AuthSources" :key="src">{{src}}</span></td>
<td class="text-center">{{user.PeerCount}}</td>
<td class="text-center">
<span v-if="user.IsAdmin" class="text-danger" :title="$t('users.admin')"><i class="fa fa-check-circle"></i></span>