mirror of
https://github.com/h44z/wg-portal.git
synced 2025-08-09 06:52: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">
|
||||
<module name="wg-portal" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<parameters value="-migrateFrom=test_source.db" />
|
||||
<parameters value="-migrateFrom=wg_portal.db" />
|
||||
<envs>
|
||||
<env name="SESSION_SECRET" value="extremlybad" />
|
||||
<env name="LOG_LEVEL" value="trace" />
|
||||
|
@ -4,6 +4,7 @@ import {userStore} from "@/stores/users";
|
||||
import {computed, ref, watch} from "vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
import {freshUser} from "@/helpers/models";
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -30,34 +31,14 @@ const title = computed(() => {
|
||||
return t("users.new")
|
||||
})
|
||||
|
||||
const formData = ref(freshFormData())
|
||||
|
||||
function freshFormData() {
|
||||
return {
|
||||
Identifier: "",
|
||||
|
||||
Email: "",
|
||||
Source: "db",
|
||||
IsAdmin: false,
|
||||
|
||||
Firstname: "",
|
||||
Lastname: "",
|
||||
Phone: "",
|
||||
Department: "",
|
||||
Notes: "",
|
||||
|
||||
Password: "",
|
||||
|
||||
Disabled: false,
|
||||
}
|
||||
}
|
||||
const formData = ref(freshUser())
|
||||
|
||||
// functions
|
||||
|
||||
watch(() => props.visible, async (newValue, oldValue) => {
|
||||
if (oldValue === false && newValue === true) { // if modal is shown
|
||||
if (!selectedUser.value) {
|
||||
formData.value = freshFormData()
|
||||
formData.value = freshUser()
|
||||
} else { // fill existing userdata
|
||||
formData.value.Identifier = selectedUser.value.Identifier
|
||||
formData.value.Email = selectedUser.value.Email
|
||||
@ -76,7 +57,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
||||
)
|
||||
|
||||
function close() {
|
||||
formData.value = freshFormData()
|
||||
formData.value = freshUser()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
@ -115,7 +96,7 @@ async function del() {
|
||||
<template>
|
||||
<Modal :title="title" :visible="visible" @close="close">
|
||||
<template #default>
|
||||
<fieldset>
|
||||
<fieldset v-if="formData.Source==='db'">
|
||||
<legend class="mt-4">General</legend>
|
||||
<div v-if="props.userId==='#NEW#'" class="form-group">
|
||||
<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>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset v-if="formData.Source==='db'">
|
||||
<legend class="mt-4">User Information</legend>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.useredit.email') }}</label>
|
||||
@ -169,9 +150,13 @@ async function del() {
|
||||
<legend class="mt-4">State</legend>
|
||||
<div class="form-check form-switch">
|
||||
<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 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">
|
||||
<label class="form-check-label">Is Admin</label>
|
||||
</div>
|
||||
@ -180,7 +165,7 @@ async function del() {
|
||||
</template>
|
||||
<template #footer>
|
||||
<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>
|
||||
<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>
|
||||
|
@ -66,7 +66,7 @@ function close() {
|
||||
<div id="user" class="tab-pane fade active show">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
User Information:
|
||||
<h4>User Information:</h4>
|
||||
<table class="table table-sm table-borderless device-status-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
@ -89,11 +89,19 @@ function close() {
|
||||
<td>{{ $t('users.label.department') }}:</td>
|
||||
<td>{{selectedUser.Department}}</td>
|
||||
</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>
|
||||
</table>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
Notes:
|
||||
<li class="list-group-item" v-if="selectedUser.Notes">
|
||||
<h4>Notes:</h4>
|
||||
<table class="table table-sm table-borderless device-status-table">
|
||||
<tbody>
|
||||
<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() {
|
||||
return {
|
||||
IsConnected: false,
|
||||
|
@ -15,18 +15,6 @@ const viewedUserId = ref("")
|
||||
onMounted(() => {
|
||||
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>
|
||||
|
||||
<template>
|
||||
@ -62,6 +50,7 @@ function editUser(user) {
|
||||
<th scope="col">
|
||||
<input id="flexCheckDefault" class="form-check-input" title="Select all" type="checkbox" value="">
|
||||
</th><!-- select -->
|
||||
<th scope="col"></th><!-- status -->
|
||||
<th scope="col">{{ $t('user.id') }}</th>
|
||||
<th scope="col">{{ $t('user.email') }}</th>
|
||||
<th scope="col">{{ $t('user.firstname') }}</th>
|
||||
@ -77,6 +66,10 @@ function editUser(user) {
|
||||
<th scope="row">
|
||||
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
|
||||
</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.Email}}</td>
|
||||
<td>{{user.Firstname}}</td>
|
||||
@ -89,7 +82,7 @@ function editUser(user) {
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<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>
|
||||
</tr>
|
||||
</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 {
|
||||
userInfo := domain.GetUserInfo(ctx)
|
||||
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 {
|
||||
return err // return any error will roll back
|
||||
}
|
||||
@ -274,7 +275,7 @@ func (r *SqlRepo) SaveInterface(ctx context.Context, id domain.InterfaceIdentifi
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.upsertInterface(tx, in)
|
||||
err = r.upsertInterface(userInfo, tx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -289,12 +290,14 @@ func (r *SqlRepo) SaveInterface(ctx context.Context, id domain.InterfaceIdentifi
|
||||
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
|
||||
|
||||
// interfaceDefaults will be applied to newly created interface records
|
||||
interfaceDefaults := domain.Interface{
|
||||
BaseModel: domain.BaseModel{
|
||||
CreatedBy: ui.UserId(),
|
||||
UpdatedBy: ui.UserId(),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
@ -309,7 +312,10 @@ func (r *SqlRepo) getOrCreateInterface(tx *gorm.DB, id domain.InterfaceIdentifie
|
||||
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
|
||||
if err != nil {
|
||||
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 {
|
||||
userInfo := domain.GetUserInfo(ctx)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
err = r.upsertPeer(tx, peer)
|
||||
err = r.upsertPeer(userInfo, tx, peer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -465,12 +472,14 @@ func (r *SqlRepo) SavePeer(ctx context.Context, id domain.PeerIdentifier, update
|
||||
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
|
||||
|
||||
// interfaceDefaults will be applied to newly created interface records
|
||||
interfaceDefaults := domain.Peer{
|
||||
BaseModel: domain.BaseModel{
|
||||
CreatedBy: ui.UserId(),
|
||||
UpdatedBy: ui.UserId(),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
@ -485,7 +494,10 @@ func (r *SqlRepo) getOrCreatePeer(tx *gorm.DB, id domain.PeerIdentifier) (*domai
|
||||
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
|
||||
if err != nil {
|
||||
return err
|
||||
@ -626,7 +638,7 @@ func (r *SqlRepo) SaveUser(ctx context.Context, id domain.UserIdentifier, update
|
||||
userInfo := domain.GetUserInfo(ctx)
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
user.UpdatedAt = time.Now()
|
||||
user.UpdatedBy = string(userInfo.Id)
|
||||
|
||||
err = r.upsertUser(tx, user)
|
||||
err = r.upsertUser(userInfo, tx, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -663,14 +672,14 @@ func (r *SqlRepo) DeleteUser(ctx context.Context, id domain.UserIdentifier) erro
|
||||
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
|
||||
|
||||
// userDefaults will be applied to newly created user records
|
||||
userDefaults := domain.User{
|
||||
BaseModel: domain.BaseModel{
|
||||
CreatedBy: creator,
|
||||
UpdatedBy: creator,
|
||||
CreatedBy: ui.UserId(),
|
||||
UpdatedBy: ui.UserId(),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
@ -687,7 +696,10 @@ func (r *SqlRepo) getOrCreateUser(creator string, tx *gorm.DB, id domain.UserIde
|
||||
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
|
||||
if err != nil {
|
||||
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";
|
||||
</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">
|
||||
</head>
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
|
@ -22,6 +22,8 @@ type User struct {
|
||||
Password string `json:"Password,omitempty"`
|
||||
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
|
||||
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
|
||||
|
||||
@ -43,6 +45,8 @@ func NewUser(src *domain.User) *User {
|
||||
Password: "", // never fill password
|
||||
Disabled: src.IsDisabled(),
|
||||
DisabledReason: src.DisabledReason,
|
||||
Locked: src.IsLocked(),
|
||||
LockedReason: src.LockedReason,
|
||||
|
||||
PeerCount: src.LinkedPeerCount,
|
||||
}
|
||||
@ -73,6 +77,8 @@ func NewDomainUser(src *User) *domain.User {
|
||||
Password: domain.PrivateString(src.Password),
|
||||
Disabled: nil, // set below
|
||||
DisabledReason: src.DisabledReason,
|
||||
Locked: nil, // set below
|
||||
LockedReason: src.LockedReason,
|
||||
LinkedPeerCount: src.PeerCount,
|
||||
}
|
||||
|
||||
@ -80,5 +86,9 @@ func NewDomainUser(src *User) *domain.User {
|
||||
res.Disabled = &now
|
||||
}
|
||||
|
||||
if src.Locked {
|
||||
res.Locked = &now
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
@ -159,6 +159,10 @@ func (a *Authenticator) IsUserValid(ctx context.Context, id domain.UserIdentifie
|
||||
return false
|
||||
}
|
||||
|
||||
if user.IsLocked() {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -193,6 +197,9 @@ func (a *Authenticator) passwordAuthentication(ctx context.Context, identifier d
|
||||
userInDatabase = true
|
||||
userSource = existingUser.Source
|
||||
}
|
||||
if userInDatabase && (existingUser.IsLocked() || existingUser.IsDisabled()) {
|
||||
return nil, errors.New("user is locked")
|
||||
}
|
||||
|
||||
if !userInDatabase || userSource == domain.UserSourceLdap {
|
||||
// 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)
|
||||
}
|
||||
|
||||
if user.IsLocked() || user.IsDisabled() {
|
||||
return nil, errors.New("user is locked")
|
||||
}
|
||||
|
||||
a.bus.Publish(app.TopicAuthLogin, user.Identifier)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
logrus.Infof("Migrated V1 database with version %s, please restart WireGuard Portal", lastVersion.Version)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -121,6 +123,8 @@ func migrateV1Users(oldDb, newDb *gorm.DB) error {
|
||||
if err := newDb.Save(&newUser).Error; err != nil {
|
||||
return fmt.Errorf("failed to migrate user %s: %w", oldUser.Email, err)
|
||||
}
|
||||
|
||||
logrus.Debugf(" - User %s migrated", newUser.Identifier)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -213,6 +217,8 @@ func migrateV1Interfaces(oldDb, newDb *gorm.DB) error {
|
||||
if err := newDb.Save(&newInterface).Error; err != nil {
|
||||
return fmt.Errorf("failed to migrate device %s: %w", oldDevice.DeviceName, err)
|
||||
}
|
||||
|
||||
logrus.Debugf(" - Interface %s migrated", newInterface.Identifier)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -302,13 +308,15 @@ func migrateV1Peers(oldDb, newDb *gorm.DB) error {
|
||||
ProviderName: "",
|
||||
IsAdmin: false,
|
||||
Locked: &now,
|
||||
LockedReason: "migration dummy user",
|
||||
LockedReason: domain.DisabledReasonMigrationDummy,
|
||||
Notes: "created by migration from v1",
|
||||
}
|
||||
|
||||
if err := newDb.Save(&user).Error; err != nil {
|
||||
return fmt.Errorf("failed to migrate dummy user %s: %w", oldPeer.Email, err)
|
||||
}
|
||||
|
||||
logrus.Debugf(" - Dummy User %s migrated", user.Identifier)
|
||||
}
|
||||
newPeer := domain.Peer{
|
||||
BaseModel: domain.BaseModel{
|
||||
@ -379,6 +387,8 @@ func migrateV1Peers(oldDb, newDb *gorm.DB) error {
|
||||
if err := newDb.Save(&newPeer).Error; err != nil {
|
||||
return fmt.Errorf("failed to migrate peer %s (%s): %w", oldPeer.Identifier, oldPeer.PublicKey, err)
|
||||
}
|
||||
|
||||
logrus.Debugf(" - Peer %s migrated", newPeer.Identifier)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -208,7 +208,7 @@ func (m Manager) validateModifications(ctx context.Context, old, new *domain.Use
|
||||
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)
|
||||
}
|
||||
|
||||
@ -224,6 +224,10 @@ func (m Manager) validateModifications(ctx context.Context, old, new *domain.Use
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
|
||||
if err := del.EditAllowed(); err != nil {
|
||||
if err := del.DeleteAllowed(); err != nil {
|
||||
return fmt.Errorf("no access: %w", err)
|
||||
}
|
||||
|
||||
|
@ -22,13 +22,14 @@ func (PrivateString) String() string {
|
||||
}
|
||||
|
||||
const (
|
||||
DisabledReasonExpired = "expired"
|
||||
DisabledReasonUserEdit = "user edit action"
|
||||
DisabledReasonUserCreate = "user create action"
|
||||
DisabledReasonAdminEdit = "admin edit action"
|
||||
DisabledReasonAdminCreate = "admin create action"
|
||||
DisabledReasonApiEdit = "api edit action"
|
||||
DisabledReasonApiCreate = "api create action"
|
||||
DisabledReasonLdapMissing = "missing in ldap"
|
||||
DisabledReasonUserMissing = "missing user"
|
||||
DisabledReasonExpired = "expired"
|
||||
DisabledReasonUserEdit = "user edit action"
|
||||
DisabledReasonUserCreate = "user create action"
|
||||
DisabledReasonAdminEdit = "admin edit action"
|
||||
DisabledReasonAdminCreate = "admin create action"
|
||||
DisabledReasonApiEdit = "api edit action"
|
||||
DisabledReasonApiCreate = "api create action"
|
||||
DisabledReasonLdapMissing = "missing in ldap"
|
||||
DisabledReasonUserMissing = "missing user"
|
||||
DisabledReasonMigrationDummy = "migration dummy user"
|
||||
)
|
||||
|
@ -2,6 +2,7 @@ package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -18,6 +19,14 @@ type ContextUserInfo struct {
|
||||
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 {
|
||||
return &ContextUserInfo{
|
||||
Id: CtxUnknownUserId,
|
||||
|
@ -37,18 +37,25 @@ type User struct {
|
||||
|
||||
// optional, integrated password authentication
|
||||
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
|
||||
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
|
||||
|
||||
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 {
|
||||
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 {
|
||||
if u.Source == UserSourceDatabase {
|
||||
return nil
|
||||
@ -57,12 +64,35 @@ func (u *User) CanChangePassword() error {
|
||||
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 {
|
||||
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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user