user/interface deletion, wip profile, wip peer endpoints

This commit is contained in:
Christoph Haas 2023-06-20 21:27:34 +02:00
parent bd89258e81
commit 9a5efdc627
16 changed files with 505 additions and 61 deletions

View File

@ -72,9 +72,7 @@ const languageFlag = computed(() => {
<a aria-expanded="false" aria-haspopup="true" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#"
role="button">{{ auth.User.Firstname }} {{ auth.User.Lastname }}</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="/user/profile">
<i class="fas fa-user"></i> {{ $t('menu.profile') }}
</a>
<RouterLink :to="{ name: 'profile' }" class="dropdown-item"><i class="fas fa-user"></i> {{ $t('menu.profile') }}</RouterLink>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" @click.prevent="auth.Logout">
<i class="fas fa-sign-out-alt"></i> {{ $t('menu.logout') }}

View File

@ -441,7 +441,7 @@ async function del() {
<div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.endpoint') }}</label>
<input v-model="formData.PeerDefEndpoint" class="form-control" placeholder="Endpoint Addresses" type="text">
<small class="form-text text-muted">Peers will get IP addresses from those subnets.</small>
<small class="form-text text-muted">The endpoint address that peers will connect to.</small>
</div>
<div class="form-group">
<label class="form-label mt-4">{{ $t('modals.interfaceedit.defaults.networks') }}</label>

View File

@ -54,13 +54,6 @@ const title = computed(() => {
const formData = ref(freshFormData())
const notificationData = ref({
title: "",
content: "",
cause: "",
type: "normal",
})
function freshFormData() {
return {
@ -141,30 +134,25 @@ function freshFormData() {
watch(() => props.visible, async (newValue, oldValue) => {
if (oldValue === false && newValue === true) { // if modal is shown
console.log(selectedInterface.value)
console.log(selectedPeer.value)
if (!selectedPeer.value) {
await loadNewPeerData()
await peers.PreparePeer(selectedInterface.value.Identifier)
formData.value.Disabled = peers.Prepared.Disabled
formData.value.Identifier = peers.Prepared.Identifier
formData.value.DisplayName = peers.Prepared.DisplayName
} else { // fill existing data
formData.value.Disabled = selectedPeer.value.Disabled
formData.value.Identifier = selectedPeer.value.Identifier
formData.value.DisplayName = selectedPeer.value.DisplayName
}
}
}
)
async function loadNewPeerData() {
console.log("loading new peer data...")
notify({
title: "Authorization",
text: "You have been logged in!",
})
notify({
title: "Authorization2",
text: "You have been logged in!",
})
notify({
title: "Authorization3",
text: "You have been logged in!",
})
}
function close() {
formData.value = freshFormData()
emit('close')

View File

@ -90,8 +90,8 @@ async function save() {
close()
} catch (e) {
notify({
title: "Backend Connection Failure",
text: "Failed to save user!",
title: "Failed to save user!",
text: e.toString(),
type: 'error',
})
}
@ -103,8 +103,8 @@ async function del() {
close()
} catch (e) {
notify({
title: "Backend Connection Failure",
text: "Failed to delete user!",
title: "Failed to delete user!",
text: e.toString(),
type: 'error',
})
}

View File

@ -39,6 +39,14 @@ const router = createRouter({
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/UserView.vue')
},
{
path: '/profile',
name: 'profile',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/ProfileView.vue')
}
],
linkActiveClass: "active",

View File

@ -63,7 +63,7 @@ export const interfaceStore = defineStore({
})
})
},
async InterfaceConfig(id) {
async InterfaceConfig(id) {
return apiWrapper.get(`${baseUrl}/config/${id}`)
.then(this.setInterfaceConfig)
.catch(error => {
@ -74,7 +74,7 @@ export const interfaceStore = defineStore({
text: "Failed to load interface configuration!",
})
})
},
},
async DeleteInterface(id) {
this.fetching = true
return apiWrapper.delete(`${baseUrl}/${id}`)

View File

@ -9,6 +9,9 @@ export const peerStore = defineStore({
id: 'peers',
state: () => ({
peers: [],
prepared: {
Identifier: "",
},
filter: "",
pageSize: 10,
pageOffset: 0,
@ -20,6 +23,7 @@ export const peerStore = defineStore({
return (id) => state.peers.find((p) => p.Identifier === id)
},
Count: (state) => state.peers.length,
Prepared: (state) => {console.log("STATE:", state.prepared); return state.prepared},
FilteredCount: (state) => state.Filtered.length,
All: (state) => state.peers,
Filtered: (state) => {
@ -71,6 +75,21 @@ export const peerStore = defineStore({
this.calculatePages()
this.fetching = false
},
setPreparedPeer(peer) {
this.prepared = peer;
},
async PreparePeer(interfaceId) {
return apiWrapper.get(`${baseUrl}/prepare/${interfaceId}`)
.then(this.setPreparedPeer)
.catch(error => {
this.prepared = {}
console.log("Failed to load prepared peer: ", error)
notify({
title: "Backend Connection Failure",
text: "Failed to load prepared peer!",
})
})
},
async LoadPeers() {
let iface = interfaceStore().GetSelected
if (!iface) {

View File

@ -0,0 +1,108 @@
import { defineStore } from 'pinia'
import {apiWrapper} from "@/helpers/fetch-wrapper";
import {notify} from "@kyvg/vue3-notification";
import {authStore} from "@/stores/auth";
const baseUrl = `/user`
export const profileStore = defineStore({
id: 'profile',
state: () => ({
peers: [],
user: {},
filter: "",
pageSize: 10,
pageOffset: 0,
pages: [],
fetching: false,
}),
getters: {
FindPeers: (state) => {
return (id) => state.peers.find((p) => p.Identifier === id)
},
CountPeers: (state) => state.peers.length,
FilteredPeerCount: (state) => state.FilteredPeers.length,
Peers: (state) => state.peers,
FilteredPeers: (state) => {
if (!state.filter) {
return state.peers
}
return state.peers.filter((p) => {
return p.DisplayName.includes(state.filter) || p.Identifier.includes(state.filter)
})
},
FilteredAndPagedPeers: (state) => {
return state.FilteredPeers.slice(state.pageOffset, state.pageOffset + state.pageSize)
},
isFetching: (state) => state.fetching,
hasNextPage: (state) => state.pageOffset < (state.FilteredPeerCount - state.pageSize),
hasPrevPage: (state) => state.pageOffset > 0,
currentPage: (state) => (state.pageOffset / state.pageSize)+1,
},
actions: {
afterPageSizeChange() {
// reset pageOffset to avoid problems with new page sizes
this.pageOffset = 0
this.calculatePages()
},
calculatePages() {
let pageCounter = 1;
this.pages = []
for (let i = 0; i < this.FilteredPeerCount; i+=this.pageSize) {
this.pages.push(pageCounter++)
}
},
gotoPage(page) {
this.pageOffset = (page-1) * this.pageSize
this.calculatePages()
},
nextPage() {
this.pageOffset += this.pageSize
this.calculatePages()
},
previousPage() {
this.pageOffset -= this.pageSize
this.calculatePages()
},
setPeers(peers) {
this.peers = peers
this.fetching = false
},
setUser(user) {
this.user = user
this.fetching = false
},
async LoadPeers() {
this.fetching = true
let currentUser = authStore().user.Identifier
return apiWrapper.get(`${baseUrl}/${currentUser}/peers`)
.then(this.setPeers)
.catch(error => {
this.setPeers([])
console.log("Failed to load user peers for ", currentUser, ": ", error)
notify({
title: "Backend Connection Failure",
text: "Failed to load user peers!",
})
})
},
async LoadUser() {
this.fetching = true
let currentUser = authStore().user.Identifier
return apiWrapper.get(`${baseUrl}/${currentUser}`)
.then(this.setUser)
.catch(error => {
this.setUser({})
console.log("Failed to load user for ", currentUser, ": ", error)
notify({
title: "Backend Connection Failure",
text: "Failed to load user!",
})
})
},
}
})

View File

@ -0,0 +1,111 @@
<script setup>
import PeerViewModal from "../components/PeerViewModal.vue";
import {computed, onMounted, ref} from "vue";
import {profileStore} from "@/stores/profile";
const profile = profileStore()
const viewedPeerId = ref("")
const editPeerId = ref("")
onMounted(async () => {
await profile.LoadUser()
await profile.LoadPeers()
})
</script>
<template>
<PeerViewModal :peerId="viewedPeerId" :visible="viewedPeerId!==''" @close="viewedPeerId=''"></PeerViewModal>
<!-- Peer list -->
<div class="mt-4 row">
<div class="col-12 col-lg-5">
<h2 class="mt-2">{{ $t('profile.h2-clients') }}</h2>
</div>
<div class="col-12 col-lg-4 text-lg-end">
<div class="form-group d-inline">
<div class="input-group mb-3">
<input v-model="profile.filter" class="form-control" placeholder="Search..." type="text" @keyup="profile.afterPageSizeChange">
<button class="input-group-text btn btn-primary" title="Search"><i class="fa-solid fa-search"></i></button>
</div>
</div>
</div>
<div class="col-12 col-lg-3 text-lg-end">
<a class="btn btn-primary ms-2" href="#" title="Add a peer" @click.prevent="editPeerId='#NEW#'"><i class="fa fa-plus me-1"></i><i class="fa fa-user"></i></a>
</div>
</div>
<div class="mt-2 table-responsive">
<div v-if="profile.CountPeers===0">
<h4>{{ $t('profile.noPeerSelect.h4') }}</h4>
<p>{{ $t('profile.noPeerSelect.message') }}</p>
</div>
<table v-if="profile.CountPeers!==0" id="peerTable" class="table table-sm">
<thead>
<tr>
<th scope="col">
<input id="flexCheckDefault" class="form-check-input" title="Select all" type="checkbox" value="">
</th><!-- select -->
<th scope="col">{{ $t('profile.tableHeadings[0]') }}</th>
<th scope="col">{{ $t('profile.tableHeadings[1]') }}</th>
<th scope="col">{{ $t('profile.tableHeadings[2]') }}</th>
<th scope="col">{{ $t('profile.tableHeadings[3]') }}</th>
<th scope="col">{{ $t('profile.tableHeadings[4]') }}</th>
<th scope="col"></th><!-- Actions -->
</tr>
</thead>
<tbody>
<tr v-for="peer in profile.FilteredAndPagedPeers" :key="peer.Identifier">
<th scope="row">
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
</th>
<td>{{peer.DisplayName}}</td>
<td>{{peer.Identifier}}</td>
<td>{{peer.UserIdentifier}}</td>
<td>
<span v-for="ip in peer.Addresses" :key="ip" class="badge rounded-pill bg-light">{{ ip }}</span>
</td>
<td>{{peer.LastConnected}}</td>
<td class="text-center">
<a href="#" title="Show peer" @click.prevent="viewedPeerId=peer.Identifier"><i class="fas fa-eye me-2"></i></a>
<a href="#" title="Edit peer" @click.prevent="editPeerId=peer.Identifier"><i class="fas fa-cog"></i></a>
</td>
</tr>
</tbody>
</table>
</div>
<hr>
<div class="mt-3">
<div class="row">
<div class="col-6">
<ul class="pagination pagination-sm">
<li :class="{disabled:profile.pageOffset===0}" class="page-item">
<a class="page-link" @click="profile.previousPage">&laquo;</a>
</li>
<li v-for="page in profile.pages" :key="page" :class="{active:profile.currentPage===page}" class="page-item">
<a class="page-link" @click="profile.gotoPage(page)">{{page}}</a>
</li>
<li :class="{disabled:!profile.hasNextPage}" class="page-item">
<a class="page-link" @click="profile.nextPage">&raquo;</a>
</li>
</ul>
</div>
<div class="col-6">
<div class="form-group row">
<label class="col-sm-6 col-form-label text-end" for="paginationSelector">{{ $t('profile.pagination.size') }}:</label>
<div class="col-sm-6">
<select v-model.number="profile.pageSize" class="form-select" @click="profile.afterPageSizeChange()">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="999999999">{{ $t('profile.pagination.all') }}</option>
</select>
</div>
</div>
</div>
</div>
</div>
</template>

View File

@ -24,6 +24,7 @@ func (e interfaceEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *aut
apiGroup.GET("/all", e.handleAllGet())
apiGroup.GET("/get/:id", e.handleSingleGet())
apiGroup.PUT("/:id", e.handleUpdatePut())
apiGroup.DELETE("/:id", e.handleDelete())
apiGroup.POST("/new", e.handleCreatePost())
apiGroup.GET("/config/:id", e.handleConfigGet())
@ -252,3 +253,36 @@ func (e interfaceEndpoint) handlePeersGet() gin.HandlerFunc {
c.JSON(http.StatusOK, model.NewPeers(peers))
}
}
// handleDelete returns a gorm handler function.
//
// @ID interfaces_handleDelete
// @Tags Interface
// @Summary Delete the interface record.
// @Produce json
// @Param id path string true "The interface identifier"
// @Success 204 "No content if deletion was successful"
// @Failure 400 {object} model.Error
// @Failure 500 {object} model.Error
// @Router /interface/{id} [delete]
func (e interfaceEndpoint) handleDelete() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing interface id"})
return
}
err := e.app.DeleteInterface(ctx, domain.InterfaceIdentifier(id))
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(),
})
return
}
c.Status(http.StatusNoContent)
}
}

View File

@ -1,13 +1,11 @@
package handlers
import (
model2 "github.com/h44z/wg-portal/internal/app/api/v0/model"
"net/http"
"github.com/h44z/wg-portal/internal/domain"
"github.com/gin-gonic/gin"
"github.com/h44z/wg-portal/internal/app"
"github.com/h44z/wg-portal/internal/app/api/v0/model"
"github.com/h44z/wg-portal/internal/domain"
"net/http"
)
type userEndpoint struct {
@ -23,7 +21,9 @@ func (e userEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti
apiGroup := g.Group("/user", e.authenticator.LoggedIn())
apiGroup.GET("/all", e.handleAllGet())
apiGroup.GET("/:id", e.handleSingleGet())
apiGroup.PUT("/:id", e.handleUpdatePut())
apiGroup.DELETE("/:id", e.handleDelete())
apiGroup.POST("/new", e.handleCreatePost())
apiGroup.GET("/:id/peers", e.handlePeersGet())
}
@ -36,16 +36,46 @@ func (e userEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti
// @Produce json
// @Success 200 {object} []model.User
// @Failure 500 {object} model.Error
// @Router /users [get]
// @Router /user/all [get]
func (e userEndpoint) handleAllGet() gin.HandlerFunc {
return func(c *gin.Context) {
users, err := e.app.GetAllUsers(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, model2.Error{Code: http.StatusInternalServerError, Message: err.Error()})
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, model2.NewUsers(users))
c.JSON(http.StatusOK, model.NewUsers(users))
}
}
// handleSingleGet returns a gorm handler function.
//
// @ID users_handleSingleGet
// @Tags Users
// @Summary Get a single user record.
// @Produce json
// @Param id path string true "The user identifier"
// @Success 200 {object} model.User
// @Failure 500 {object} model.Error
// @Router /user/{id} [get]
func (e userEndpoint) handleSingleGet() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing user id"})
return
}
user, err := e.app.GetUser(ctx, domain.UserIdentifier(id))
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, model.NewUser(user))
}
}
@ -60,36 +90,36 @@ func (e userEndpoint) handleAllGet() gin.HandlerFunc {
// @Success 200 {object} model.User
// @Failure 400 {object} model.Error
// @Failure 500 {object} model.Error
// @Router /users/{id} [put]
// @Router /user/{id} [put]
func (e userEndpoint) handleUpdatePut() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, model2.Error{Code: http.StatusBadRequest, Message: "missing user id"})
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing user id"})
return
}
var user model2.User
var user model.User
err := c.BindJSON(&user)
if err != nil {
c.JSON(http.StatusBadRequest, model2.Error{Code: http.StatusBadRequest, Message: err.Error()})
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
return
}
if id != user.Identifier {
c.JSON(http.StatusBadRequest, model2.Error{Code: http.StatusBadRequest, Message: "user id mismatch"})
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "user id mismatch"})
return
}
updateUser, err := e.app.UpdateUser(ctx, model2.NewDomainUser(&user))
updateUser, err := e.app.UpdateUser(ctx, model.NewDomainUser(&user))
if err != nil {
c.JSON(http.StatusInternalServerError, model2.Error{Code: http.StatusInternalServerError, Message: err.Error()})
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, model2.NewUser(updateUser))
c.JSON(http.StatusOK, model.NewUser(updateUser))
}
}
@ -103,25 +133,25 @@ func (e userEndpoint) handleUpdatePut() gin.HandlerFunc {
// @Success 200 {object} model.User
// @Failure 400 {object} model.Error
// @Failure 500 {object} model.Error
// @Router /users/new [post]
// @Router /user/new [post]
func (e userEndpoint) handleCreatePost() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c)
var user model2.User
var user model.User
err := c.BindJSON(&user)
if err != nil {
c.JSON(http.StatusBadRequest, model2.Error{Code: http.StatusBadRequest, Message: err.Error()})
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
return
}
newUser, err := e.app.CreateUser(ctx, model2.NewDomainUser(&user))
newUser, err := e.app.CreateUser(ctx, model.NewDomainUser(&user))
if err != nil {
c.JSON(http.StatusInternalServerError, model2.Error{Code: http.StatusInternalServerError, Message: err.Error()})
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, model2.NewUser(newUser))
c.JSON(http.StatusOK, model.NewUser(newUser))
}
}
@ -133,21 +163,52 @@ func (e userEndpoint) handleCreatePost() gin.HandlerFunc {
// @Produce json
// @Success 200 {object} []model.Peer
// @Failure 500 {object} model.Error
// @Router /users/{id}/peers [get]
// @Router /user/{id}/peers [get]
func (e userEndpoint) handlePeersGet() gin.HandlerFunc {
return func(c *gin.Context) {
interfaceId := c.Param("id")
if interfaceId == "" {
c.JSON(http.StatusBadRequest, model2.Error{Code: http.StatusInternalServerError, Message: "missing id parameter"})
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusInternalServerError, Message: "missing id parameter"})
return
}
peers, err := e.app.GetUserPeers(c.Request.Context(), domain.UserIdentifier(interfaceId))
if err != nil {
c.JSON(http.StatusInternalServerError, model2.Error{Code: http.StatusInternalServerError, Message: err.Error()})
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, model2.NewPeers(peers))
c.JSON(http.StatusOK, model.NewPeers(peers))
}
}
// handleDelete returns a gorm handler function.
//
// @ID users_handleDelete
// @Tags Users
// @Summary Delete the user record.
// @Produce json
// @Param id path string true "The user identifier"
// @Success 204 "No content if deletion was successful"
// @Failure 400 {object} model.Error
// @Failure 500 {object} model.Error
// @Router /user/{id} [delete]
func (e userEndpoint) handleDelete() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c)
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing user id"})
return
}
err := e.app.DeleteUser(ctx, domain.UserIdentifier(id))
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.Status(http.StatusNoContent)
}
}

View File

@ -3,4 +3,5 @@ package app
const TopicUserCreated = "user:created"
const TopicUserRegistered = "user:registered"
const TopicUserDisabled = "user:disabled"
const TopicUserDeleted = "user:deleted"
const TopicAuthLogin = "auth:login"

View File

@ -21,6 +21,7 @@ type UserManager interface {
GetAllUsers(ctx context.Context) ([]domain.User, error)
UpdateUser(ctx context.Context, user *domain.User) (*domain.User, error)
CreateUser(ctx context.Context, user *domain.User) (*domain.User, error)
DeleteUser(ctx context.Context, id domain.UserIdentifier) error
}
type WireGuardManager interface {
@ -34,6 +35,7 @@ type WireGuardManager interface {
PrepareInterface(ctx context.Context) (*domain.Interface, error)
CreateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error)
UpdateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error)
DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error
}
type StatisticsCollector interface {

View File

@ -181,9 +181,33 @@ func (m Manager) CreateUser(ctx context.Context, user *domain.User) (*domain.Use
return user, nil
}
func (m Manager) DeleteUser(ctx context.Context, id domain.UserIdentifier) error {
existingUser, err := m.users.GetUser(ctx, id)
if err != nil && !errors.Is(err, domain.ErrNotFound) {
return fmt.Errorf("unable to find user %s: %w", id, err)
}
if err := m.validateDeletion(ctx, existingUser); err != nil {
return fmt.Errorf("deletion not allowed: %w", err)
}
err = m.users.DeleteUser(ctx, id)
if err != nil {
return fmt.Errorf("deletion failure: %w", err)
}
m.bus.Publish(app.TopicUserDeleted, existingUser)
return nil
}
func (m Manager) validateModifications(ctx context.Context, old, new *domain.User) error {
currentUser := domain.GetUserInfo(ctx)
if currentUser.Id != new.Identifier && !currentUser.IsAdmin {
return fmt.Errorf("insufficient permissions")
}
if err := old.EditAllowed(); err != nil {
return fmt.Errorf("no access: %w", err)
}
@ -208,10 +232,20 @@ func (m Manager) validateModifications(ctx context.Context, old, new *domain.Use
}
func (m Manager) validateCreation(ctx context.Context, new *domain.User) error {
currentUser := domain.GetUserInfo(ctx)
if !currentUser.IsAdmin {
return fmt.Errorf("insufficient permissions")
}
if new.Identifier == "" {
return fmt.Errorf("invalid user identifier")
}
if new.Identifier == "all" { // the all user identifier collides with the rest api routes
return fmt.Errorf("reserved user identifier")
}
if new.Source != domain.UserSourceDatabase {
return fmt.Errorf("invalid user source: %s", new.Source)
}
@ -223,6 +257,24 @@ func (m Manager) validateCreation(ctx context.Context, new *domain.User) error {
return nil
}
func (m Manager) validateDeletion(ctx context.Context, del *domain.User) error {
currentUser := domain.GetUserInfo(ctx)
if !currentUser.IsAdmin {
return fmt.Errorf("insufficient permissions")
}
if err := del.EditAllowed(); err != nil {
return fmt.Errorf("no access: %w", err)
}
if currentUser.Id == del.Identifier {
return fmt.Errorf("cannot delete own user")
}
return nil
}
func (m Manager) runLdapSynchronizationService(ctx context.Context) {
running := true
for running {

View File

@ -524,6 +524,34 @@ func (m Manager) UpdateInterface(ctx context.Context, in *domain.Interface) (*do
return in, nil
}
func (m Manager) DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error {
existingInterface, err := m.db.GetInterface(ctx, id)
if err != nil {
return fmt.Errorf("unable to find interface %s: %w", id, err)
}
if err := m.validateDeletion(ctx, existingInterface); err != nil {
return fmt.Errorf("deletion not allowed: %w", err)
}
err = m.deleteInterfacePeers(ctx, id)
if err != nil {
return fmt.Errorf("peer deletion failure: %w", err)
}
err = m.wg.DeleteInterface(ctx, id)
if err != nil {
return fmt.Errorf("wireguard deletion failure: %w", err)
}
err = m.db.DeleteInterface(ctx, id)
if err != nil {
return fmt.Errorf("deletion failure: %w", err)
}
return nil
}
func (m Manager) validateModifications(ctx context.Context, old, new *domain.Interface) error {
currentUser := domain.GetUserInfo(ctx)
@ -547,3 +575,33 @@ func (m Manager) validateCreation(ctx context.Context, old, new *domain.Interfac
return nil
}
func (m Manager) validateDeletion(ctx context.Context, del *domain.Interface) error {
currentUser := domain.GetUserInfo(ctx)
if !currentUser.IsAdmin {
return fmt.Errorf("insufficient permissions")
}
return nil
}
func (m Manager) deleteInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier) error {
allPeers, err := m.db.GetInterfacePeers(ctx, id)
if err != nil {
return err
}
for _, peer := range allPeers {
err = m.wg.DeletePeer(ctx, id, peer.Identifier)
if err != nil {
return fmt.Errorf("wireguard peer deletion failure for %s: %w", peer.Identifier, err)
}
err = m.db.DeletePeer(ctx, peer.Identifier)
if err != nil {
return fmt.Errorf("peer deletion failure for %s: %w", peer.Identifier, err)
}
}
return nil
}

View File

@ -70,6 +70,10 @@ func (u *User) CheckPassword(password string) error {
return errors.New("invalid user source")
}
if u.IsDisabled() {
return errors.New("user disabled")
}
if u.Password == "" {
return errors.New("empty user password")
}