mirror of
https://github.com/h44z/wg-portal.git
synced 2025-08-10 07:22:24 +00:00
many more improvements and cleanup
This commit is contained in:
parent
2a5b4fe31d
commit
984818c393
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="wg-portal-migrate" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
<configuration default="false" name="wg-portal-migrate" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
||||||
<module name="wg-portal" />
|
<module name="wg-portal" />
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
<working_directory value="$PROJECT_DIR$" />
|
||||||
<parameters value="-migrateFrom=test_source.db" />
|
<parameters value="-migrateFrom=wg_portal.db" />
|
||||||
<envs>
|
<envs>
|
||||||
<env name="SESSION_SECRET" value="extremlybad" />
|
<env name="SESSION_SECRET" value="extremlybad" />
|
||||||
<env name="LOG_LEVEL" value="trace" />
|
<env name="LOG_LEVEL" value="trace" />
|
||||||
|
@ -4,6 +4,7 @@ import {userStore} from "@/stores/users";
|
|||||||
import {computed, ref, watch} from "vue";
|
import {computed, ref, watch} from "vue";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { notify } from "@kyvg/vue3-notification";
|
import { notify } from "@kyvg/vue3-notification";
|
||||||
|
import {freshUser} from "@/helpers/models";
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@ -30,34 +31,14 @@ const title = computed(() => {
|
|||||||
return t("users.new")
|
return t("users.new")
|
||||||
})
|
})
|
||||||
|
|
||||||
const formData = ref(freshFormData())
|
const formData = ref(freshUser())
|
||||||
|
|
||||||
function freshFormData() {
|
|
||||||
return {
|
|
||||||
Identifier: "",
|
|
||||||
|
|
||||||
Email: "",
|
|
||||||
Source: "db",
|
|
||||||
IsAdmin: false,
|
|
||||||
|
|
||||||
Firstname: "",
|
|
||||||
Lastname: "",
|
|
||||||
Phone: "",
|
|
||||||
Department: "",
|
|
||||||
Notes: "",
|
|
||||||
|
|
||||||
Password: "",
|
|
||||||
|
|
||||||
Disabled: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// functions
|
// functions
|
||||||
|
|
||||||
watch(() => props.visible, async (newValue, oldValue) => {
|
watch(() => props.visible, async (newValue, oldValue) => {
|
||||||
if (oldValue === false && newValue === true) { // if modal is shown
|
if (oldValue === false && newValue === true) { // if modal is shown
|
||||||
if (!selectedUser.value) {
|
if (!selectedUser.value) {
|
||||||
formData.value = freshFormData()
|
formData.value = freshUser()
|
||||||
} else { // fill existing userdata
|
} else { // fill existing userdata
|
||||||
formData.value.Identifier = selectedUser.value.Identifier
|
formData.value.Identifier = selectedUser.value.Identifier
|
||||||
formData.value.Email = selectedUser.value.Email
|
formData.value.Email = selectedUser.value.Email
|
||||||
@ -76,7 +57,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
formData.value = freshFormData()
|
formData.value = freshUser()
|
||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +96,7 @@ async function del() {
|
|||||||
<template>
|
<template>
|
||||||
<Modal :title="title" :visible="visible" @close="close">
|
<Modal :title="title" :visible="visible" @close="close">
|
||||||
<template #default>
|
<template #default>
|
||||||
<fieldset>
|
<fieldset v-if="formData.Source==='db'">
|
||||||
<legend class="mt-4">General</legend>
|
<legend class="mt-4">General</legend>
|
||||||
<div v-if="props.userId==='#NEW#'" class="form-group">
|
<div v-if="props.userId==='#NEW#'" class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.useredit.identifier') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.useredit.identifier') }}</label>
|
||||||
@ -131,7 +112,7 @@ async function del() {
|
|||||||
<small v-if="props.userId!=='#NEW#'" id="passwordHelp" class="form-text text-muted">Leave this field blank to keep current password.</small>
|
<small v-if="props.userId!=='#NEW#'" id="passwordHelp" class="form-text text-muted">Leave this field blank to keep current password.</small>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset v-if="formData.Source==='db'">
|
||||||
<legend class="mt-4">User Information</legend>
|
<legend class="mt-4">User Information</legend>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.useredit.email') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.useredit.email') }}</label>
|
||||||
@ -169,9 +150,13 @@ async function del() {
|
|||||||
<legend class="mt-4">State</legend>
|
<legend class="mt-4">State</legend>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input v-model="formData.Disabled" class="form-check-input" type="checkbox">
|
<input v-model="formData.Disabled" class="form-check-input" type="checkbox">
|
||||||
<label class="form-check-label" >Disabled</label>
|
<label class="form-check-label" >Disabled (no WireGuard connection and no login possible)</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
|
<input v-model="formData.Locked" class="form-check-input" type="checkbox">
|
||||||
|
<label class="form-check-label" >Locked (no login possible, WireGuard connections still work)</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch" v-if="formData.Source==='db'">
|
||||||
<input v-model="formData.IsAdmin" checked="" class="form-check-input" type="checkbox">
|
<input v-model="formData.IsAdmin" checked="" class="form-check-input" type="checkbox">
|
||||||
<label class="form-check-label">Is Admin</label>
|
<label class="form-check-label">Is Admin</label>
|
||||||
</div>
|
</div>
|
||||||
@ -180,7 +165,7 @@ async function del() {
|
|||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex-fill text-start">
|
<div class="flex-fill text-start">
|
||||||
<button v-if="props.userId!=='#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">Delete</button>
|
<button v-if="props.userId!=='#NEW#'&&formData.Source==='db'" class="btn btn-danger me-1" type="button" @click.prevent="del">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary me-1" type="button" @click.prevent="save">Save</button>
|
<button class="btn btn-primary me-1" type="button" @click.prevent="save">Save</button>
|
||||||
<button class="btn btn-secondary" type="button" @click.prevent="close">Discard</button>
|
<button class="btn btn-secondary" type="button" @click.prevent="close">Discard</button>
|
||||||
|
@ -66,7 +66,7 @@ function close() {
|
|||||||
<div id="user" class="tab-pane fade active show">
|
<div id="user" class="tab-pane fade active show">
|
||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
User Information:
|
<h4>User Information:</h4>
|
||||||
<table class="table table-sm table-borderless device-status-table">
|
<table class="table table-sm table-borderless device-status-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@ -89,11 +89,19 @@ function close() {
|
|||||||
<td>{{ $t('users.label.department') }}:</td>
|
<td>{{ $t('users.label.department') }}:</td>
|
||||||
<td>{{selectedUser.Department}}</td>
|
<td>{{selectedUser.Department}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr v-if="selectedUser.Disabled">
|
||||||
|
<td>{{ $t('users.label.disabled') }}:</td>
|
||||||
|
<td>{{selectedUser.DisabledReason}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="selectedUser.Locked">
|
||||||
|
<td>{{ $t('users.label.locked') }}:</td>
|
||||||
|
<td>{{selectedUser.LockedReason}}</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item" v-if="selectedUser.Notes">
|
||||||
Notes:
|
<h4>Notes:</h4>
|
||||||
<table class="table table-sm table-borderless device-status-table">
|
<table class="table table-sm table-borderless device-status-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>{{selectedUser.Notes}}</td></tr>
|
<tr><td>{{selectedUser.Notes}}</td></tr>
|
||||||
|
@ -122,6 +122,29 @@ export function freshPeer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function freshUser() {
|
||||||
|
return {
|
||||||
|
Identifier: "",
|
||||||
|
|
||||||
|
Email: "",
|
||||||
|
Source: "db",
|
||||||
|
IsAdmin: false,
|
||||||
|
|
||||||
|
Firstname: "",
|
||||||
|
Lastname: "",
|
||||||
|
Phone: "",
|
||||||
|
Department: "",
|
||||||
|
Notes: "",
|
||||||
|
|
||||||
|
Password: "",
|
||||||
|
|
||||||
|
Disabled: false,
|
||||||
|
DisabledReason: "",
|
||||||
|
Locked: false,
|
||||||
|
LockedReason: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function freshStats() {
|
export function freshStats() {
|
||||||
return {
|
return {
|
||||||
IsConnected: false,
|
IsConnected: false,
|
||||||
|
@ -15,18 +15,6 @@ const viewedUserId = ref("")
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
users.LoadUsers()
|
users.LoadUsers()
|
||||||
})
|
})
|
||||||
|
|
||||||
function editUser(user) {
|
|
||||||
if(user.Source === 'db') {
|
|
||||||
editUserId.value = user.Identifier
|
|
||||||
} else {
|
|
||||||
notify({
|
|
||||||
title: "Forbidden",
|
|
||||||
text: "You can not edit this user!",
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -62,6 +50,7 @@ function editUser(user) {
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
<input id="flexCheckDefault" class="form-check-input" title="Select all" type="checkbox" value="">
|
<input id="flexCheckDefault" class="form-check-input" title="Select all" type="checkbox" value="">
|
||||||
</th><!-- select -->
|
</th><!-- select -->
|
||||||
|
<th scope="col"></th><!-- status -->
|
||||||
<th scope="col">{{ $t('user.id') }}</th>
|
<th scope="col">{{ $t('user.id') }}</th>
|
||||||
<th scope="col">{{ $t('user.email') }}</th>
|
<th scope="col">{{ $t('user.email') }}</th>
|
||||||
<th scope="col">{{ $t('user.firstname') }}</th>
|
<th scope="col">{{ $t('user.firstname') }}</th>
|
||||||
@ -77,6 +66,10 @@ function editUser(user) {
|
|||||||
<th scope="row">
|
<th scope="row">
|
||||||
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
|
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
|
||||||
</th>
|
</th>
|
||||||
|
<td class="text-center">
|
||||||
|
<span v-if="user.Disabled" class="text-danger"><i class="fa fa-circle-xmark" :title="user.DisabledReason"></i></span>
|
||||||
|
<span v-if="user.Locked" class="text-danger"><i class="fas fa-lock" :title="user.LockedReason"></i></span>
|
||||||
|
</td>
|
||||||
<td>{{user.Identifier}}</td>
|
<td>{{user.Identifier}}</td>
|
||||||
<td>{{user.Email}}</td>
|
<td>{{user.Email}}</td>
|
||||||
<td>{{user.Firstname}}</td>
|
<td>{{user.Firstname}}</td>
|
||||||
@ -89,7 +82,7 @@ function editUser(user) {
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="#" title="Show user" @click.prevent="viewedUserId=user.Identifier"><i class="fas fa-eye me-2"></i></a>
|
<a href="#" title="Show user" @click.prevent="viewedUserId=user.Identifier"><i class="fas fa-eye me-2"></i></a>
|
||||||
<a :class="{disabled:user.Source!=='db'}" href="#" title="Edit user" @click.prevent="editUser(user)"><i class="fas fa-cog me-2"></i></a>
|
<a href="#" title="Edit user" @click.prevent="editUserId=user.Identifier"><i class="fas fa-cog me-2"></i></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -263,8 +263,9 @@ func (r *SqlRepo) FindInterfaces(ctx context.Context, search string) ([]domain.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *SqlRepo) SaveInterface(ctx context.Context, id domain.InterfaceIdentifier, updateFunc func(in *domain.Interface) (*domain.Interface, error)) error {
|
func (r *SqlRepo) SaveInterface(ctx context.Context, id domain.InterfaceIdentifier, updateFunc func(in *domain.Interface) (*domain.Interface, error)) error {
|
||||||
|
userInfo := domain.GetUserInfo(ctx)
|
||||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
in, err := r.getOrCreateInterface(tx, id)
|
in, err := r.getOrCreateInterface(userInfo, tx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // return any error will roll back
|
return err // return any error will roll back
|
||||||
}
|
}
|
||||||
@ -274,7 +275,7 @@ func (r *SqlRepo) SaveInterface(ctx context.Context, id domain.InterfaceIdentifi
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.upsertInterface(tx, in)
|
err = r.upsertInterface(userInfo, tx, in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -289,12 +290,14 @@ func (r *SqlRepo) SaveInterface(ctx context.Context, id domain.InterfaceIdentifi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SqlRepo) getOrCreateInterface(tx *gorm.DB, id domain.InterfaceIdentifier) (*domain.Interface, error) {
|
func (r *SqlRepo) getOrCreateInterface(ui *domain.ContextUserInfo, tx *gorm.DB, id domain.InterfaceIdentifier) (*domain.Interface, error) {
|
||||||
var in domain.Interface
|
var in domain.Interface
|
||||||
|
|
||||||
// interfaceDefaults will be applied to newly created interface records
|
// interfaceDefaults will be applied to newly created interface records
|
||||||
interfaceDefaults := domain.Interface{
|
interfaceDefaults := domain.Interface{
|
||||||
BaseModel: domain.BaseModel{
|
BaseModel: domain.BaseModel{
|
||||||
|
CreatedBy: ui.UserId(),
|
||||||
|
UpdatedBy: ui.UserId(),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
},
|
},
|
||||||
@ -309,7 +312,10 @@ func (r *SqlRepo) getOrCreateInterface(tx *gorm.DB, id domain.InterfaceIdentifie
|
|||||||
return &in, nil
|
return &in, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SqlRepo) upsertInterface(tx *gorm.DB, in *domain.Interface) error {
|
func (r *SqlRepo) upsertInterface(ui *domain.ContextUserInfo, tx *gorm.DB, in *domain.Interface) error {
|
||||||
|
in.UpdatedBy = ui.UserId()
|
||||||
|
in.UpdatedAt = time.Now()
|
||||||
|
|
||||||
err := tx.Save(in).Error
|
err := tx.Save(in).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -439,8 +445,9 @@ func (r *SqlRepo) FindUserPeers(ctx context.Context, id domain.UserIdentifier, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *SqlRepo) SavePeer(ctx context.Context, id domain.PeerIdentifier, updateFunc func(in *domain.Peer) (*domain.Peer, error)) error {
|
func (r *SqlRepo) SavePeer(ctx context.Context, id domain.PeerIdentifier, updateFunc func(in *domain.Peer) (*domain.Peer, error)) error {
|
||||||
|
userInfo := domain.GetUserInfo(ctx)
|
||||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
peer, err := r.getOrCreatePeer(tx, id)
|
peer, err := r.getOrCreatePeer(userInfo, tx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // return any error will roll back
|
return err // return any error will roll back
|
||||||
}
|
}
|
||||||
@ -450,7 +457,7 @@ func (r *SqlRepo) SavePeer(ctx context.Context, id domain.PeerIdentifier, update
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.upsertPeer(tx, peer)
|
err = r.upsertPeer(userInfo, tx, peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -465,12 +472,14 @@ func (r *SqlRepo) SavePeer(ctx context.Context, id domain.PeerIdentifier, update
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SqlRepo) getOrCreatePeer(tx *gorm.DB, id domain.PeerIdentifier) (*domain.Peer, error) {
|
func (r *SqlRepo) getOrCreatePeer(ui *domain.ContextUserInfo, tx *gorm.DB, id domain.PeerIdentifier) (*domain.Peer, error) {
|
||||||
var peer domain.Peer
|
var peer domain.Peer
|
||||||
|
|
||||||
// interfaceDefaults will be applied to newly created interface records
|
// interfaceDefaults will be applied to newly created interface records
|
||||||
interfaceDefaults := domain.Peer{
|
interfaceDefaults := domain.Peer{
|
||||||
BaseModel: domain.BaseModel{
|
BaseModel: domain.BaseModel{
|
||||||
|
CreatedBy: ui.UserId(),
|
||||||
|
UpdatedBy: ui.UserId(),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
},
|
},
|
||||||
@ -485,7 +494,10 @@ func (r *SqlRepo) getOrCreatePeer(tx *gorm.DB, id domain.PeerIdentifier) (*domai
|
|||||||
return &peer, nil
|
return &peer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SqlRepo) upsertPeer(tx *gorm.DB, peer *domain.Peer) error {
|
func (r *SqlRepo) upsertPeer(ui *domain.ContextUserInfo, tx *gorm.DB, peer *domain.Peer) error {
|
||||||
|
peer.UpdatedBy = ui.UserId()
|
||||||
|
peer.UpdatedAt = time.Now()
|
||||||
|
|
||||||
err := tx.Save(peer).Error
|
err := tx.Save(peer).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -626,7 +638,7 @@ func (r *SqlRepo) SaveUser(ctx context.Context, id domain.UserIdentifier, update
|
|||||||
userInfo := domain.GetUserInfo(ctx)
|
userInfo := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
user, err := r.getOrCreateUser(string(userInfo.Id), tx, id)
|
user, err := r.getOrCreateUser(userInfo, tx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // return any error will roll back
|
return err // return any error will roll back
|
||||||
}
|
}
|
||||||
@ -636,10 +648,7 @@ func (r *SqlRepo) SaveUser(ctx context.Context, id domain.UserIdentifier, update
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
user.UpdatedAt = time.Now()
|
err = r.upsertUser(userInfo, tx, user)
|
||||||
user.UpdatedBy = string(userInfo.Id)
|
|
||||||
|
|
||||||
err = r.upsertUser(tx, user)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -663,14 +672,14 @@ func (r *SqlRepo) DeleteUser(ctx context.Context, id domain.UserIdentifier) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SqlRepo) getOrCreateUser(creator string, tx *gorm.DB, id domain.UserIdentifier) (*domain.User, error) {
|
func (r *SqlRepo) getOrCreateUser(ui *domain.ContextUserInfo, tx *gorm.DB, id domain.UserIdentifier) (*domain.User, error) {
|
||||||
var user domain.User
|
var user domain.User
|
||||||
|
|
||||||
// userDefaults will be applied to newly created user records
|
// userDefaults will be applied to newly created user records
|
||||||
userDefaults := domain.User{
|
userDefaults := domain.User{
|
||||||
BaseModel: domain.BaseModel{
|
BaseModel: domain.BaseModel{
|
||||||
CreatedBy: creator,
|
CreatedBy: ui.UserId(),
|
||||||
UpdatedBy: creator,
|
UpdatedBy: ui.UserId(),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
},
|
},
|
||||||
@ -687,7 +696,10 @@ func (r *SqlRepo) getOrCreateUser(creator string, tx *gorm.DB, id domain.UserIde
|
|||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SqlRepo) upsertUser(tx *gorm.DB, user *domain.User) error {
|
func (r *SqlRepo) upsertUser(ui *domain.ContextUserInfo, tx *gorm.DB, user *domain.User) error {
|
||||||
|
user.UpdatedBy = ui.UserId()
|
||||||
|
user.UpdatedAt = time.Now()
|
||||||
|
|
||||||
err := tx.Save(user).Error
|
err := tx.Save(user).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -14,7 +14,7 @@
|
|||||||
let WGPORTAL_SITE_COMPANY_NAME="WireGuard Portal";
|
let WGPORTAL_SITE_COMPANY_NAME="WireGuard Portal";
|
||||||
</script>
|
</script>
|
||||||
<script src="/api/v0/config/frontend.js"></script>
|
<script src="/api/v0/config/frontend.js"></script>
|
||||||
<script type="module" crossorigin src="/app/assets/index-4f7c99b3.js"></script>
|
<script type="module" crossorigin src="/app/assets/index-96214b1b.js"></script>
|
||||||
<link rel="stylesheet" href="/app/assets/index-7144f109.css">
|
<link rel="stylesheet" href="/app/assets/index-7144f109.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="d-flex flex-column min-vh-100">
|
<body class="d-flex flex-column min-vh-100">
|
||||||
|
@ -22,6 +22,8 @@ type User struct {
|
|||||||
Password string `json:"Password,omitempty"`
|
Password string `json:"Password,omitempty"`
|
||||||
Disabled bool `json:"Disabled"` // if this field is set, the user is disabled
|
Disabled bool `json:"Disabled"` // if this field is set, the user is disabled
|
||||||
DisabledReason string `json:"DisabledReason"` // the reason why the user has been disabled
|
DisabledReason string `json:"DisabledReason"` // the reason why the user has been disabled
|
||||||
|
Locked bool `json:"Locked"` // if this field is set, the user is locked
|
||||||
|
LockedReason string `json:"LockedReason"` // the reason why the user has been locked
|
||||||
|
|
||||||
// Calculated
|
// Calculated
|
||||||
|
|
||||||
@ -43,6 +45,8 @@ func NewUser(src *domain.User) *User {
|
|||||||
Password: "", // never fill password
|
Password: "", // never fill password
|
||||||
Disabled: src.IsDisabled(),
|
Disabled: src.IsDisabled(),
|
||||||
DisabledReason: src.DisabledReason,
|
DisabledReason: src.DisabledReason,
|
||||||
|
Locked: src.IsLocked(),
|
||||||
|
LockedReason: src.LockedReason,
|
||||||
|
|
||||||
PeerCount: src.LinkedPeerCount,
|
PeerCount: src.LinkedPeerCount,
|
||||||
}
|
}
|
||||||
@ -73,6 +77,8 @@ func NewDomainUser(src *User) *domain.User {
|
|||||||
Password: domain.PrivateString(src.Password),
|
Password: domain.PrivateString(src.Password),
|
||||||
Disabled: nil, // set below
|
Disabled: nil, // set below
|
||||||
DisabledReason: src.DisabledReason,
|
DisabledReason: src.DisabledReason,
|
||||||
|
Locked: nil, // set below
|
||||||
|
LockedReason: src.LockedReason,
|
||||||
LinkedPeerCount: src.PeerCount,
|
LinkedPeerCount: src.PeerCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,5 +86,9 @@ func NewDomainUser(src *User) *domain.User {
|
|||||||
res.Disabled = &now
|
res.Disabled = &now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if src.Locked {
|
||||||
|
res.Locked = &now
|
||||||
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -159,6 +159,10 @@ func (a *Authenticator) IsUserValid(ctx context.Context, id domain.UserIdentifie
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.IsLocked() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,6 +197,9 @@ func (a *Authenticator) passwordAuthentication(ctx context.Context, identifier d
|
|||||||
userInDatabase = true
|
userInDatabase = true
|
||||||
userSource = existingUser.Source
|
userSource = existingUser.Source
|
||||||
}
|
}
|
||||||
|
if userInDatabase && (existingUser.IsLocked() || existingUser.IsDisabled()) {
|
||||||
|
return nil, errors.New("user is locked")
|
||||||
|
}
|
||||||
|
|
||||||
if !userInDatabase || userSource == domain.UserSourceLdap {
|
if !userInDatabase || userSource == domain.UserSourceLdap {
|
||||||
// search user in ldap if registration is enabled
|
// search user in ldap if registration is enabled
|
||||||
@ -313,6 +320,10 @@ func (a *Authenticator) OauthLoginStep2(ctx context.Context, providerId, nonce,
|
|||||||
return nil, fmt.Errorf("unable to process user information: %w", err)
|
return nil, fmt.Errorf("unable to process user information: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.IsLocked() || user.IsDisabled() {
|
||||||
|
return nil, errors.New("user is locked")
|
||||||
|
}
|
||||||
|
|
||||||
a.bus.Publish(app.TopicAuthLogin, user.Identifier)
|
a.bus.Publish(app.TopicAuthLogin, user.Identifier)
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
|
@ -62,6 +62,8 @@ func migrateFromV1(cfg *config.Config, db *gorm.DB, source, typ string) error {
|
|||||||
return fmt.Errorf("peer migration failed: %w", err)
|
return fmt.Errorf("peer migration failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.Infof("Migrated V1 database with version %s, please restart WireGuard Portal", lastVersion.Version)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +123,8 @@ func migrateV1Users(oldDb, newDb *gorm.DB) error {
|
|||||||
if err := newDb.Save(&newUser).Error; err != nil {
|
if err := newDb.Save(&newUser).Error; err != nil {
|
||||||
return fmt.Errorf("failed to migrate user %s: %w", oldUser.Email, err)
|
return fmt.Errorf("failed to migrate user %s: %w", oldUser.Email, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.Debugf(" - User %s migrated", newUser.Identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -213,6 +217,8 @@ func migrateV1Interfaces(oldDb, newDb *gorm.DB) error {
|
|||||||
if err := newDb.Save(&newInterface).Error; err != nil {
|
if err := newDb.Save(&newInterface).Error; err != nil {
|
||||||
return fmt.Errorf("failed to migrate device %s: %w", oldDevice.DeviceName, err)
|
return fmt.Errorf("failed to migrate device %s: %w", oldDevice.DeviceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.Debugf(" - Interface %s migrated", newInterface.Identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -302,13 +308,15 @@ func migrateV1Peers(oldDb, newDb *gorm.DB) error {
|
|||||||
ProviderName: "",
|
ProviderName: "",
|
||||||
IsAdmin: false,
|
IsAdmin: false,
|
||||||
Locked: &now,
|
Locked: &now,
|
||||||
LockedReason: "migration dummy user",
|
LockedReason: domain.DisabledReasonMigrationDummy,
|
||||||
Notes: "created by migration from v1",
|
Notes: "created by migration from v1",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := newDb.Save(&user).Error; err != nil {
|
if err := newDb.Save(&user).Error; err != nil {
|
||||||
return fmt.Errorf("failed to migrate dummy user %s: %w", oldPeer.Email, err)
|
return fmt.Errorf("failed to migrate dummy user %s: %w", oldPeer.Email, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.Debugf(" - Dummy User %s migrated", user.Identifier)
|
||||||
}
|
}
|
||||||
newPeer := domain.Peer{
|
newPeer := domain.Peer{
|
||||||
BaseModel: domain.BaseModel{
|
BaseModel: domain.BaseModel{
|
||||||
@ -379,6 +387,8 @@ func migrateV1Peers(oldDb, newDb *gorm.DB) error {
|
|||||||
if err := newDb.Save(&newPeer).Error; err != nil {
|
if err := newDb.Save(&newPeer).Error; err != nil {
|
||||||
return fmt.Errorf("failed to migrate peer %s (%s): %w", oldPeer.Identifier, oldPeer.PublicKey, err)
|
return fmt.Errorf("failed to migrate peer %s (%s): %w", oldPeer.Identifier, oldPeer.PublicKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.Debugf(" - Peer %s migrated", newPeer.Identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -208,7 +208,7 @@ func (m Manager) validateModifications(ctx context.Context, old, new *domain.Use
|
|||||||
return fmt.Errorf("insufficient permissions")
|
return fmt.Errorf("insufficient permissions")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := old.EditAllowed(); err != nil {
|
if err := old.EditAllowed(new); err != nil {
|
||||||
return fmt.Errorf("no access: %w", err)
|
return fmt.Errorf("no access: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,6 +224,10 @@ func (m Manager) validateModifications(ctx context.Context, old, new *domain.Use
|
|||||||
return fmt.Errorf("cannot disable own user")
|
return fmt.Errorf("cannot disable own user")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if currentUser.Id == old.Identifier && new.IsLocked() {
|
||||||
|
return fmt.Errorf("cannot lock own user")
|
||||||
|
}
|
||||||
|
|
||||||
if old.Source != new.Source {
|
if old.Source != new.Source {
|
||||||
return fmt.Errorf("cannot change user source")
|
return fmt.Errorf("cannot change user source")
|
||||||
}
|
}
|
||||||
@ -264,7 +268,7 @@ func (m Manager) validateDeletion(ctx context.Context, del *domain.User) error {
|
|||||||
return fmt.Errorf("insufficient permissions")
|
return fmt.Errorf("insufficient permissions")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := del.EditAllowed(); err != nil {
|
if err := del.DeleteAllowed(); err != nil {
|
||||||
return fmt.Errorf("no access: %w", err)
|
return fmt.Errorf("no access: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,13 +22,14 @@ func (PrivateString) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DisabledReasonExpired = "expired"
|
DisabledReasonExpired = "expired"
|
||||||
DisabledReasonUserEdit = "user edit action"
|
DisabledReasonUserEdit = "user edit action"
|
||||||
DisabledReasonUserCreate = "user create action"
|
DisabledReasonUserCreate = "user create action"
|
||||||
DisabledReasonAdminEdit = "admin edit action"
|
DisabledReasonAdminEdit = "admin edit action"
|
||||||
DisabledReasonAdminCreate = "admin create action"
|
DisabledReasonAdminCreate = "admin create action"
|
||||||
DisabledReasonApiEdit = "api edit action"
|
DisabledReasonApiEdit = "api edit action"
|
||||||
DisabledReasonApiCreate = "api create action"
|
DisabledReasonApiCreate = "api create action"
|
||||||
DisabledReasonLdapMissing = "missing in ldap"
|
DisabledReasonLdapMissing = "missing in ldap"
|
||||||
DisabledReasonUserMissing = "missing user"
|
DisabledReasonUserMissing = "missing user"
|
||||||
|
DisabledReasonMigrationDummy = "migration dummy user"
|
||||||
)
|
)
|
||||||
|
@ -2,6 +2,7 @@ package domain
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -18,6 +19,14 @@ type ContextUserInfo struct {
|
|||||||
IsAdmin bool
|
IsAdmin bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ContextUserInfo) String() string {
|
||||||
|
return fmt.Sprintf("%s|%t", u.Id, u.IsAdmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ContextUserInfo) UserId() string {
|
||||||
|
return string(u.Id)
|
||||||
|
}
|
||||||
|
|
||||||
func DefaultContextUserInfo() *ContextUserInfo {
|
func DefaultContextUserInfo() *ContextUserInfo {
|
||||||
return &ContextUserInfo{
|
return &ContextUserInfo{
|
||||||
Id: CtxUnknownUserId,
|
Id: CtxUnknownUserId,
|
||||||
|
@ -37,18 +37,25 @@ type User struct {
|
|||||||
|
|
||||||
// optional, integrated password authentication
|
// optional, integrated password authentication
|
||||||
Password PrivateString `form:"password" binding:"omitempty"`
|
Password PrivateString `form:"password" binding:"omitempty"`
|
||||||
Disabled *time.Time `gorm:"index;column:disabled"` // if this field is set, the user is disabled
|
Disabled *time.Time `gorm:"index;column:disabled"` // if this field is set, the user is disabled (WireGuard peers are disabled as well)
|
||||||
DisabledReason string // the reason why the user has been disabled
|
DisabledReason string // the reason why the user has been disabled
|
||||||
Locked *time.Time `gorm:"index;column:locked"` // if this field is set, the user is locked and can no longer login
|
Locked *time.Time `gorm:"index;column:locked"` // if this field is set, the user is locked and can no longer login (WireGuard peers still can connect)
|
||||||
LockedReason string // the reason why the user has been locked
|
LockedReason string // the reason why the user has been locked
|
||||||
|
|
||||||
LinkedPeerCount int `gorm:"-"`
|
LinkedPeerCount int `gorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsDisabled returns true if the user is disabled. In such a case,
|
||||||
|
// no login is possible and WireGuard peers associated with the user are disabled.
|
||||||
func (u *User) IsDisabled() bool {
|
func (u *User) IsDisabled() bool {
|
||||||
return u.Disabled != nil
|
return u.Disabled != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsLocked returns true if the user is locked. In such a case, no login is possible, WireGuard connections still work.
|
||||||
|
func (u *User) IsLocked() bool {
|
||||||
|
return u.Locked != nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) CanChangePassword() error {
|
func (u *User) CanChangePassword() error {
|
||||||
if u.Source == UserSourceDatabase {
|
if u.Source == UserSourceDatabase {
|
||||||
return nil
|
return nil
|
||||||
@ -57,12 +64,35 @@ func (u *User) CanChangePassword() error {
|
|||||||
return errors.New("password change only allowed for database source")
|
return errors.New("password change only allowed for database source")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) EditAllowed() error {
|
func (u *User) EditAllowed(new *User) error {
|
||||||
if u.Source == UserSourceDatabase {
|
if u.Source == UserSourceDatabase {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New("edit only allowed for database source")
|
// for users which are not database users, only the notes field and the disabled flag can be updated
|
||||||
|
updateOk := true
|
||||||
|
updateOk = updateOk && u.Identifier == new.Identifier
|
||||||
|
updateOk = updateOk && u.Source == new.Source
|
||||||
|
updateOk = updateOk && u.IsAdmin == new.IsAdmin
|
||||||
|
updateOk = updateOk && u.Email == new.Email
|
||||||
|
updateOk = updateOk && u.Firstname == new.Firstname
|
||||||
|
updateOk = updateOk && u.Lastname == new.Lastname
|
||||||
|
updateOk = updateOk && u.Phone == new.Phone
|
||||||
|
updateOk = updateOk && u.Department == new.Department
|
||||||
|
|
||||||
|
if !updateOk {
|
||||||
|
return errors.New("edit only allowed for database source")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) DeleteAllowed() error {
|
||||||
|
if u.Source == UserSourceDatabase {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("delete only allowed for database source")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) CheckPassword(password string) error {
|
func (u *User) CheckPassword(password string) error {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user