new package dbHandler accesscontrol memeberdb and login with rights
All checks were successful
Build Quasar SPA and Go Backend for memberApp / build-spa (push) Successful in 2m20s
Build Quasar SPA and Go Backend for memberApp / build-backend (amd64, .exe, windows) (push) Successful in 5m27s
Build Quasar SPA and Go Backend for memberApp / build-backend (amd64, , linux) (push) Successful in 5m32s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm, 6, , linux) (push) Successful in 5m28s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm64, , linux) (push) Successful in 5m29s

This commit is contained in:
Adrian Zürcher
2025-10-31 14:54:05 +01:00
parent b0d6bb5512
commit cc3a547961
60 changed files with 1062 additions and 1162 deletions

View File

@@ -29,6 +29,16 @@ const localTitle = ref('');
const localField = ref('');
const value = ref('');
const props = defineProps({
endpoint: {
type: String,
required: true,
},
queryId: {
type: Boolean,
},
});
const emit = defineEmits(['update']);
const { NotifyResponse } = useNotify();
@@ -42,7 +52,10 @@ function open(label: string, field: string, member: Member) {
}
function save() {
const query = 'secure/members/edit?id=' + localMember.value.id;
let query = props.endpoint;
if (props.queryId) {
query += '?id=' + localMember.value.id;
}
let payload = {};
if (value.value === localMember.value[localField.value]) {
@@ -50,13 +63,15 @@ function save() {
return;
}
payload = {
id: localMember.value.id,
[localField.value]: value.value,
};
appApi
.post(query, payload)
.then(() => {
emit('update', '');
.then((resp) => {
emit('update');
NotifyResponse(resp.data);
dialog.value.close();
})
.catch((err) => {

View File

@@ -161,9 +161,9 @@ async function save() {
if (!valid) return;
let query = 'secure/members/edit?id=' + localMember.value.id;
let query = 'members/edit?id=' + localMember.value.id;
if (newMember.value) {
query = 'secure/members/add';
query = 'members/add';
}
appApi

View File

@@ -2,18 +2,29 @@
<DialogFrame
ref="dialog"
:header-title="newRole ? $t('addNewRole') : 'Edit ' + localRole.role"
:height="600"
:height="700"
:width="500"
>
<div class="row justify-center q-gutter-md">
<div class="row justify-center">
<q-input
class="q-ml-md col-5 required"
v-if="showRoleField"
class="q-my-lg col-5 required"
:label="$t('role')"
filled
:rules="[(val) => !!val || $t('roleIsRequired')]"
v-model="localRole.role"
autofocus
></q-input>
<q-card>
<q-card-section class="text-h5 text-bold text-primary flex justify-center">{{
$t('permissions')
}}</q-card-section>
<q-separator color="black" />
<PermissionsCheckBoxGroup
:permissions="localRole.permissions || []"
v-on:update="(val) => (localRole.permissions = val)"
/>
</q-card>
</div>
<div class="row justify-center">
<q-btn class="q-ma-md" color="primary" no-caps @click="save">Save</q-btn>
@@ -27,29 +38,35 @@ import { ref } from 'vue';
import { appApi } from 'src/boot/axios';
import type { Role } from 'src/vueLib/models/roles';
import { useNotify } from 'src/vueLib/general/useNotify';
import PermissionsCheckBoxGroup from 'src/vueLib/checkboxes/CheckBoxGroupPermissions.vue';
import { defaultPermissions } from 'src/vueLib/checkboxes/permissions';
import { i18n } from 'src/boot/lang';
const { NotifyResponse } = useNotify();
const dialog = ref();
const newRole = ref(false);
const showRoleField = ref(true);
const localRole = ref<Role>({
role: '',
rights: null,
permissions: [],
});
const emit = defineEmits(['update-role']);
const emit = defineEmits(['update']);
function open(role: Role | null) {
function open(role: Role | null, typ?: 'permissions') {
if (role === undefined) {
return;
}
showRoleField.value = typ !== 'permissions';
if (role !== null) {
localRole.value = role;
localRole.value.permissions = role.permissions || defaultPermissions;
newRole.value = false;
} else {
localRole.value = {
role: '',
rights: null,
permissions: defaultPermissions,
};
newRole.value = true;
}
@@ -58,15 +75,23 @@ function open(role: Role | null) {
}
function save() {
let query = 'secure/roles/edit?id=' + localRole.value.id;
let query = 'roles/update?id=' + localRole.value.id;
let update = true;
if (newRole.value) {
query = 'secure/roles/add';
query = 'roles/add';
update = false;
localRole.value.permissions = defaultPermissions;
}
appApi
.post(query, JSON.stringify(localRole.value))
.then(() => {
emit('update-role');
if (update) {
NotifyResponse(
i18n.global.t('role') + " '" + localRole.value.role + "' " + i18n.global.t('updated'),
);
}
emit('update');
dialog.value.close();
})
.catch((err) => NotifyResponse(err, 'error'));

View File

@@ -7,6 +7,7 @@
:url="`https://0.0.0.0:` + portApp + `/api/members/import/csv`"
label="Import CSV"
multiple
:with-credentials="true"
accept=".csv"
field-name="file"
method="POST"

View File

@@ -5,36 +5,100 @@
:height="600"
:width="500"
>
<div class="row justify-center q-gutter-md">
<q-input
class="q-ml-md col-5 required"
:label="$t('user')"
filled
:rules="[(val) => !!val || $t('userIsRequired')]"
v-model="localUser.user"
autofocus
></q-input>
<q-input
class="q-ml-md col-5 required"
:label="$t('email')"
filled
:rules="[(val) => !!val || $t('emailIsRequired')]"
v-model="localUser.email"
></q-input>
<q-input
class="q-ml-md col-5 required"
:label="$t('role')"
filled
:rules="[(val) => !!val || $t('roleIsRequired')]"
v-model="localUser.role"
></q-input>
<q-input
class="q-ml-md col-5"
:label="$t('expires')"
filled
v-model="localUser.expires"
></q-input>
</div>
<q-form ref="form">
<div class="row justify-center q-gutter-md">
<q-input
class="col-5 required"
:label="$t('user')"
filled
:lazy-rules="false"
:rules="[(val) => !!val || $t('userIsRequired')]"
v-model="localUser.user"
autofocus
></q-input>
<q-input
class="col-5 required"
:label="$t('email')"
filled
:lazy-rules="false"
:rules="[(val) => !!val || $t('emailIsRequired')]"
v-model="localUser.email"
></q-input>
<div class="col-5">
<q-input
class="col-5 required"
:label="$t('password')"
filled
:lazy-rules="false"
:rules="[validatePassword]"
v-model="localUser.password"
@update:model-value="checkStrength"
:type="showPassword1 ? 'text' : 'password'"
>
<template v-slot:append>
<q-btn
flat
dense
:icon="showPassword1 ? 'visibility_off' : 'visibility'"
@mousedown.prevent="showPassword1 = true"
@mouseup.prevent="showPassword1 = false"
@mouseleave.prevent="showPassword1 = false"
@touchstart.prevent="showPassword1 = true"
@touchend.prevent="showPassword1 = false"
@touchcancel.prevent="showPassword1 = false"
></q-btn>
<q-icon :name="strengthIcon" :color="strengthColor"></q-icon>
</template>
</q-input>
<div class="q-mt-md q-px-xl">
<q-linear-progress :value="strengthValue" :color="strengthColor" size="8px" rounded />
<div class="text-caption text-center q-mt-xs">
{{ strengthLabel }}
</div>
</div>
<q-input
class="col-5 required"
:label="$t('password')"
filled
:type="showPassword2 ? 'text' : 'password'"
:rules="[checkSamePassword]"
v-model="passwordCheck"
>
<template v-slot:append>
<q-btn
flat
dense
:icon="showPassword2 ? 'visibility_off' : 'visibility'"
@mousedown.prevent="showPassword2 = true"
@mouseup.prevent="showPassword2 = false"
@mouseleave.prevent="showPassword2 = false"
@touchstart.prevent="showPassword2 = true"
@touchend.prevent="showPassword2 = false"
@touchcancel.prevent="showPassword2 = false"
></q-btn>
</template>
</q-input>
</div>
<div class="col-5">
<q-select
class="col-5 required"
:label="$t('role')"
filled
:options="props.roles"
:rules="[(val) => !!val || $t('roleIsRequired')]"
v-model="localUser.role"
></q-select>
<q-input
class="col-5"
:label="$t('expires')"
filled
v-model="localUser.expires"
></q-input>
</div>
</div>
</q-form>
<div class="row justify-center">
<q-btn class="q-ma-md" color="primary" no-caps @click="save">Save</q-btn>
</div>
@@ -44,13 +108,25 @@
<script setup lang="ts">
import DialogFrame from 'src/vueLib/dialog/DialogFrame.vue';
import { ref } from 'vue';
import zxcvbn from 'zxcvbn';
import { appApi } from 'src/boot/axios';
import type { User } from 'src/vueLib/models/users';
import { useNotify } from 'src/vueLib/general/useNotify';
import { validateQForm } from 'src/vueLib/utils/validation';
import { i18n } from 'src/boot/lang';
import { DefaultSettings } from 'src/vueLib/models/settings';
const { NotifyResponse } = useNotify();
const dialog = ref();
const form = ref();
const newUser = ref(false);
const showPassword1 = ref(false);
const showPassword2 = ref(false);
const passwordCheck = ref('');
const strengthValue = ref(0);
const strengthLabel = ref('Enter a password');
const strengthColor = ref('grey');
const strengthIcon = ref('lock');
const localUser = ref<User>({
user: '',
email: '',
@@ -58,9 +134,16 @@ const localUser = ref<User>({
expires: '',
});
const emit = defineEmits(['update-user']);
const props = defineProps({
roles: {
type: Array,
required: true,
},
});
function open(user: User | null) {
const emit = defineEmits(['update']);
async function open(user: User | null) {
if (user === undefined) {
return;
}
@@ -78,19 +161,66 @@ function open(user: User | null) {
newUser.value = true;
}
dialog.value?.open();
await dialog.value?.open();
await validateQForm(form.value);
}
function save() {
let query = 'secure/users/edit?id=' + localUser.value.id;
function checkStrength() {
const result = zxcvbn(localUser.value.password || '');
strengthValue.value = (result.score + 1) / 5;
const levels = [
i18n.global.t('veryWeak'),
i18n.global.t('weak'),
i18n.global.t('fair'),
i18n.global.t('good'),
i18n.global.t('strong'),
];
const colors = ['red', 'orange', 'yellow', 'light-green', 'green'];
const icon = ['lock', 'warning', 'error_outline', 'check_circle_outline', 'verified_user'];
strengthLabel.value = levels[result.score] || i18n.global.t('veryWeak');
strengthColor.value = colors[result.score] || 'grey';
strengthIcon.value = icon[result.score] || 'lock';
}
function validatePassword(): string | boolean {
if (!localUser.value.password) return i18n.global.t('passwordIsRequired');
if (localUser.value.password.length < 8) {
return i18n.global.t('passwordTooShort');
} else if (!/[A-Z]/.test(localUser.value.password)) {
return i18n.global.t('passwordNeedsUppercase');
} else if (!/[a-z]/.test(localUser.value.password)) {
return i18n.global.t('passwordNeedsLowercase');
} else if (!/[0-9]/.test(localUser.value.password)) {
return i18n.global.t('passwordNeedsNumber');
} else if (!/[!@#$%^&*(),.?":{}|<>]/.test(localUser.value.password)) {
return i18n.global.t('passwordNeedsSpecial');
}
return true;
}
function checkSamePassword(): string | boolean {
if (localUser.value.password === passwordCheck.value) return true;
return i18n.global.t('passwordDoNotMatch');
}
async function save() {
if (!(await validateQForm(form.value))) {
NotifyResponse(i18n.global.t('notAllRequiredFieldsFilled'), 'error');
return;
}
let query = 'users/edit?id=' + localUser.value.id;
if (newUser.value) {
query = 'secure/users/add';
query = 'users/add';
localUser.value.settings = DefaultSettings();
}
appApi
.post(query, JSON.stringify(localUser.value))
.then(() => {
emit('update-user');
emit('update');
dialog.value.close();
})
.catch((err) => NotifyResponse(err, 'error'));