This commit is contained in:
Christoph 2025-01-10 13:43:04 +01:00
parent 04f87bf37a
commit 61ca0fd67a
8 changed files with 492 additions and 114 deletions

View File

@ -105,7 +105,7 @@ func main() {
apiFrontend := handlersV0.NewRestApi(cfg, backend)
apiV1BackendUsers := backendV1.NewUserService(cfg, database, database)
apiV1BackendUsers := backendV1.NewUserService(cfg, userManager)
apiV1EndpointUsers := handlersV1.NewUserEndpoint(apiV1BackendUsers)
apiV1 := handlersV1.NewRestApi(userManager, apiV1EndpointUsers)

View File

@ -40,6 +40,12 @@
}
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
@ -65,6 +71,15 @@
],
"summary": "Get a specific user record by its internal identifier.",
"operationId": "users_handleByIdGet",
"parameters": [
{
"type": "string",
"description": "The user identifier.",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
@ -72,6 +87,12 @@
"$ref": "#/definitions/models.User"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
@ -91,6 +112,204 @@
}
}
}
},
"put": {
"security": [
{
"BasicAuth": []
}
],
"description": "Only admins can update existing records.",
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Update a user record.",
"operationId": "users_handleUpdatePut",
"parameters": [
{
"type": "string",
"description": "The user identifier",
"name": "id",
"in": "path",
"required": true
},
{
"description": "The user data",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.User"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.User"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/models.Error"
}
}
}
},
"delete": {
"security": [
{
"BasicAuth": []
}
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Delete the user record.",
"operationId": "users_handleDelete",
"parameters": [
{
"type": "string",
"description": "The user identifier",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No content if deletion was successful"
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/models.Error"
}
}
}
}
},
"/user/new": {
"post": {
"security": [
{
"BasicAuth": []
}
],
"description": "Only admins can create new records.",
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Create a new user record.",
"operationId": "users_handleCreatePost",
"parameters": [
{
"description": "The user data.",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.User"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.User"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"409": {
"description": "Conflict",
"schema": {
"$ref": "#/definitions/models.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/models.Error"
}
}
}
}
}
},
@ -116,9 +335,13 @@
"type": "object",
"properties": {
"ApiEnabled": {
"description": "If this field is set, the user is allowed to use the RESTful API.",
"description": "If this field is set, the user is allowed to use the RESTful API. This field is read-only.",
"type": "boolean"
},
"ApiToken": {
"description": "The API token of the user. This field is never populated on bulk read operations.",
"type": "string"
},
"Department": {
"description": "The department of the user. This field is optional.",
"type": "string"
@ -163,8 +386,12 @@
"description": "Additional notes about the user. This field is optional.",
"type": "string"
},
"Password": {
"description": "The password of the user. This field is never populated on read operations.",
"type": "string"
},
"PeerCount": {
"description": "The number of peers linked to the user.",
"description": "The number of peers linked to the user. This field is read-only.",
"type": "integer"
},
"Phone": {

View File

@ -16,8 +16,12 @@ definitions:
properties:
ApiEnabled:
description: If this field is set, the user is allowed to use the RESTful
API.
API. This field is read-only.
type: boolean
ApiToken:
description: The API token of the user. This field is never populated on bulk
read operations.
type: string
Department:
description: The department of the user. This field is optional.
type: string
@ -52,8 +56,12 @@ definitions:
Notes:
description: Additional notes about the user. This field is optional.
type: string
Password:
description: The password of the user. This field is never populated on read
operations.
type: string
PeerCount:
description: The number of peers linked to the user.
description: The number of peers linked to the user. This field is read-only.
type: integer
Phone:
description: The phone number of the user. This field is optional.
@ -88,6 +96,10 @@ paths:
items:
$ref: '#/definitions/models.User'
type: array
"401":
description: Unauthorized
schema:
$ref: '#/definitions/models.Error'
"500":
description: Internal Server Error
schema:
@ -98,10 +110,54 @@ paths:
tags:
- Users
/user/id/{id}:
delete:
operationId: users_handleDelete
parameters:
- description: The user identifier
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"204":
description: No content if deletion was successful
"400":
description: Bad Request
schema:
$ref: '#/definitions/models.Error'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/models.Error'
"403":
description: Forbidden
schema:
$ref: '#/definitions/models.Error'
"404":
description: Not Found
schema:
$ref: '#/definitions/models.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/models.Error'
security:
- BasicAuth: []
summary: Delete the user record.
tags:
- Users
get:
description: Normal users can only access their own record. Admins can access
all records.
operationId: users_handleByIdGet
parameters:
- description: The user identifier.
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
@ -109,6 +165,10 @@ paths:
description: OK
schema:
$ref: '#/definitions/models.User'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/models.Error'
"403":
description: Forbidden
schema:
@ -126,6 +186,96 @@ paths:
summary: Get a specific user record by its internal identifier.
tags:
- Users
put:
description: Only admins can update existing records.
operationId: users_handleUpdatePut
parameters:
- description: The user identifier
in: path
name: id
required: true
type: string
- description: The user data
in: body
name: request
required: true
schema:
$ref: '#/definitions/models.User'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.User'
"400":
description: Bad Request
schema:
$ref: '#/definitions/models.Error'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/models.Error'
"403":
description: Forbidden
schema:
$ref: '#/definitions/models.Error'
"404":
description: Not Found
schema:
$ref: '#/definitions/models.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/models.Error'
security:
- BasicAuth: []
summary: Update a user record.
tags:
- Users
/user/new:
post:
description: Only admins can create new records.
operationId: users_handleCreatePost
parameters:
- description: The user data.
in: body
name: request
required: true
schema:
$ref: '#/definitions/models.User'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.User'
"400":
description: Bad Request
schema:
$ref: '#/definitions/models.Error'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/models.Error'
"403":
description: Forbidden
schema:
$ref: '#/definitions/models.Error'
"409":
description: Conflict
schema:
$ref: '#/definitions/models.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/models.Error'
security:
- BasicAuth: []
summary: Create a new user record.
tags:
- Users
securityDefinitions:
BasicAuth:
type: basic

View File

@ -4,38 +4,29 @@ import (
"context"
"errors"
"fmt"
"math"
"sync"
"github.com/h44z/wg-portal/internal/app/users"
"github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain"
)
type UserDatabaseRepo interface {
type UserManagerRepo interface {
GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
GetAllUsers(ctx context.Context) ([]domain.User, error)
FindUsers(ctx context.Context, search string) ([]domain.User, error)
SaveUser(ctx context.Context, id domain.UserIdentifier, updateFunc func(u *domain.User) (*domain.User, error)) error
CreateUser(ctx context.Context, user *domain.User) (*domain.User, error)
UpdateUser(ctx context.Context, user *domain.User) (*domain.User, error)
DeleteUser(ctx context.Context, id domain.UserIdentifier) error
}
type PeerDatabaseRepo interface {
GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error)
}
type UserService struct {
cfg *config.Config
users UserDatabaseRepo
peers PeerDatabaseRepo
users UserManagerRepo
}
func NewUserService(cfg *config.Config, users UserDatabaseRepo, peers PeerDatabaseRepo) *UserService {
func NewUserService(cfg *config.Config, users UserManagerRepo) *UserService {
return &UserService{
cfg: cfg,
users: users,
peers: peers,
}
}
@ -44,31 +35,12 @@ func (s UserService) GetUsers(ctx context.Context) ([]domain.User, error) {
return nil, err
}
users, err := s.users.GetAllUsers(ctx)
allUsers, err := s.users.GetAllUsers(ctx)
if err != nil {
return nil, fmt.Errorf("unable to load users: %w", err)
return nil, err
}
ch := make(chan *domain.User)
wg := sync.WaitGroup{}
workers := int(math.Min(float64(len(users)), 10))
wg.Add(workers)
for i := 0; i < workers; i++ {
go func() {
defer wg.Done()
for user := range ch {
peers, _ := s.peers.GetUserPeers(ctx, user.Identifier) // ignore error, list will be empty in error case
user.LinkedPeerCount = len(peers)
}
}()
}
for i := range users {
ch <- &users[i]
}
close(ch)
wg.Wait()
return users, nil
return allUsers, nil
}
func (s UserService) GetUserById(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) {
@ -76,13 +48,14 @@ func (s UserService) GetUserById(ctx context.Context, id domain.UserIdentifier)
return nil, errors.Join(err, domain.ErrNoPermission)
}
user, err := s.users.GetUser(ctx, id)
if err != nil {
return nil, fmt.Errorf("unable to load user %s: %w", id, err)
if s.cfg.Advanced.ApiAdminOnly && !domain.GetUserInfo(ctx).IsAdmin {
return nil, errors.Join(errors.New("only admins can access this endpoint"), domain.ErrNoPermission)
}
peers, _ := s.peers.GetUserPeers(ctx, user.Identifier) // ignore error, list will be empty in error case
user.LinkedPeerCount = len(peers)
user, err := s.users.GetUser(ctx, id)
if err != nil {
return nil, err
}
return user, nil
}
@ -92,30 +65,43 @@ func (s UserService) CreateUser(ctx context.Context, user *domain.User) (*domain
return nil, err
}
existingUser, err := s.users.GetUser(ctx, user.Identifier)
if err != nil && !errors.Is(err, domain.ErrNotFound) {
return nil, fmt.Errorf("unable to load existing user %s: %w", user.Identifier, err)
}
if existingUser != nil {
return nil, errors.Join(fmt.Errorf("user %s already exists", user.Identifier), domain.ErrDuplicateEntry)
}
if err := users.ValidateCreation(ctx, user); err != nil {
return nil, errors.Join(fmt.Errorf("creation not allowed: %w", err), domain.ErrInvalidData)
}
err = user.HashPassword()
createdUser, err := s.users.CreateUser(ctx, user)
if err != nil {
return nil, err
}
err = s.users.SaveUser(ctx, user.Identifier, func(u *domain.User) (*domain.User, error) {
user.CopyCalculatedAttributes(u)
return user, nil
})
if err != nil {
return nil, fmt.Errorf("creation failure: %w", err)
return createdUser, nil
}
func (s UserService) UpdateUser(ctx context.Context, id domain.UserIdentifier, user *domain.User) (
*domain.User,
error,
) {
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
return nil, err
}
return user, nil
if id != user.Identifier {
return nil, fmt.Errorf("user id mismatch: %s != %s: %w", id, user.Identifier, domain.ErrInvalidData)
}
updatedUser, err := s.users.UpdateUser(ctx, user)
if err != nil {
return nil, err
}
return updatedUser, nil
}
func (s UserService) DeleteUser(ctx context.Context, id domain.UserIdentifier) error {
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
return err
}
err := s.users.DeleteUser(ctx, id)
if err != nil {
return err
}
return nil
}

View File

@ -5,7 +5,6 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/h44z/wg-portal/internal/app/api/v0/model"
"github.com/h44z/wg-portal/internal/app/api/v1/models"
"github.com/h44z/wg-portal/internal/domain"
)
@ -14,6 +13,8 @@ type UserService interface {
GetUsers(ctx context.Context) ([]domain.User, error)
GetUserById(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
CreateUser(ctx context.Context, user *domain.User) (*domain.User, error)
UpdateUser(ctx context.Context, id domain.UserIdentifier, user *domain.User) (*domain.User, error)
DeleteUser(ctx context.Context, id domain.UserIdentifier) error
}
type UserEndpoint struct {
@ -35,6 +36,9 @@ func (e UserEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti
apiGroup.GET("/all", authenticator.LoggedIn(ScopeAdmin), e.handleAllGet())
apiGroup.GET("/id/:id", authenticator.LoggedIn(), e.handleByIdGet())
apiGroup.POST("/new", authenticator.LoggedIn(ScopeAdmin), e.handleCreatePost())
apiGroup.PUT("/id/:id", authenticator.LoggedIn(ScopeAdmin), e.handleUpdatePut())
apiGroup.DELETE("/id/:id", authenticator.LoggedIn(ScopeAdmin), e.handleDelete())
}
// handleAllGet returns a gorm Handler function.
@ -44,6 +48,7 @@ func (e UserEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti
// @Summary Get all user records.
// @Produce json
// @Success 200 {object} []models.User
// @Failure 401 {object} models.Error
// @Failure 500 {object} models.Error
// @Router /user/all [get]
// @Security BasicAuth
@ -70,7 +75,7 @@ func (e UserEndpoint) handleAllGet() gin.HandlerFunc {
// @Param id path string true "The user identifier."
// @Produce json
// @Success 200 {object} models.User
// @Failure 403 {object} models.Error
// @Failure 401 {object} models.Error
// @Failure 403 {object} models.Error
// @Failure 404 {object} models.Error
// @Failure 500 {object} models.Error
@ -106,10 +111,12 @@ func (e UserEndpoint) handleByIdGet() gin.HandlerFunc {
// @Produce json
// @Success 200 {object} models.User
// @Failure 400 {object} models.Error
// @Failure 401 {object} models.Error
// @Failure 403 {object} models.Error
// @Failure 409 {object} models.Error
// @Failure 500 {object} models.Error
// @Router /user/new [post]
// @Security BasicAuth
func (e UserEndpoint) handleCreatePost() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c)
@ -142,41 +149,36 @@ func (e UserEndpoint) handleCreatePost() gin.HandlerFunc {
// @Produce json
// @Success 200 {object} models.User
// @Failure 400 {object} models.Error
// @Failure 401 {object} models.Error
// @Failure 403 {object} models.Error
// @Failure 404 {object} models.Error
// @Failure 500 {object} models.Error
// @Router /user/{id} [put]
// @Router /user/id/{id} [put]
// @Security BasicAuth
func (e UserEndpoint) handleUpdatePut() gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: implement
ctx := domain.SetUserInfoFromGin(c)
id := Base64UrlDecode(c.Param("id"))
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing user id"})
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing user id"})
return
}
var user model.User
var user models.User
err := c.BindJSON(&user)
if err != nil {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: err.Error()})
return
}
if id != user.Identifier {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "user id mismatch"})
return
}
updateUser, err := e.app.UpdateUser(ctx, model.NewDomainUser(&user))
updateUser, err := e.users.UpdateUser(ctx, domain.UserIdentifier(id), models.NewDomainUser(&user))
if err != nil {
c.JSON(http.StatusInternalServerError,
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
c.JSON(ParseServiceError(err))
return
}
c.JSON(http.StatusOK, model.NewUser(updateUser, false))
c.JSON(http.StatusOK, models.NewUser(updateUser, true))
}
}
@ -188,24 +190,27 @@ func (e UserEndpoint) handleUpdatePut() gin.HandlerFunc {
// @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]
// @Failure 400 {object} models.Error
// @Failure 401 {object} models.Error
// @Failure 403 {object} models.Error
// @Failure 404 {object} models.Error
// @Failure 500 {object} models.Error
// @Router /user/id/{id} [delete]
// @Security BasicAuth
func (e UserEndpoint) handleDelete() gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: implement
ctx := domain.SetUserInfoFromGin(c)
id := Base64UrlDecode(c.Param("id"))
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing user id"})
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing user id"})
return
}
err := e.app.DeleteUser(ctx, domain.UserIdentifier(id))
err := e.users.DeleteUser(ctx, domain.UserIdentifier(id))
if err != nil {
c.JSON(http.StatusInternalServerError,
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
c.JSON(ParseServiceError(err))
return
}

View File

@ -102,7 +102,7 @@ func (m Manager) GetUser(ctx context.Context, id domain.UserIdentifier) (*domain
user, err := m.users.GetUser(ctx, id)
if err != nil {
return nil, fmt.Errorf("unable to load peer %s: %w", id, err)
return nil, fmt.Errorf("unable to load user %s: %w", id, err)
}
peers, _ := m.peers.GetUserPeers(ctx, id) // ignore error, list will be empty in error case
@ -194,10 +194,10 @@ func (m Manager) CreateUser(ctx context.Context, user *domain.User) (*domain.Use
return nil, fmt.Errorf("unable to load existing user %s: %w", user.Identifier, err)
}
if existingUser != nil {
return nil, fmt.Errorf("user %s already exists", user.Identifier)
return nil, errors.Join(fmt.Errorf("user %s already exists", user.Identifier), domain.ErrDuplicateEntry)
}
if err := ValidateCreation(ctx, user); err != nil {
if err := m.validateCreation(ctx, user); err != nil {
return nil, fmt.Errorf("creation not allowed: %w", err)
}
@ -302,33 +302,33 @@ func (m Manager) validateModifications(ctx context.Context, old, new *domain.Use
}
if err := old.EditAllowed(new); err != nil {
return fmt.Errorf("no access: %w", err)
return errors.Join(fmt.Errorf("no access: %w", err), domain.ErrInvalidData)
}
if err := old.CanChangePassword(); err != nil && string(new.Password) != "" {
return fmt.Errorf("no access: %w", err)
return errors.Join(fmt.Errorf("no access: %w", err), domain.ErrInvalidData)
}
if currentUser.Id == old.Identifier && old.IsAdmin && !new.IsAdmin {
return fmt.Errorf("cannot remove own admin rights")
return fmt.Errorf("cannot remove own admin rights: %w", domain.ErrInvalidData)
}
if currentUser.Id == old.Identifier && new.IsDisabled() {
return fmt.Errorf("cannot disable own user")
return fmt.Errorf("cannot disable own user: %w", domain.ErrInvalidData)
}
if currentUser.Id == old.Identifier && new.IsLocked() {
return fmt.Errorf("cannot lock own user")
return fmt.Errorf("cannot lock own user: %w", domain.ErrInvalidData)
}
if old.Source != new.Source {
return fmt.Errorf("cannot change user source")
return fmt.Errorf("cannot change user source: %w", domain.ErrInvalidData)
}
return nil
}
func ValidateCreation(ctx context.Context, new *domain.User) error {
func (m Manager) validateCreation(ctx context.Context, new *domain.User) error {
currentUser := domain.GetUserInfo(ctx)
if !currentUser.IsAdmin {
@ -336,23 +336,32 @@ func ValidateCreation(ctx context.Context, new *domain.User) error {
}
if new.Identifier == "" {
return fmt.Errorf("invalid user identifier")
return fmt.Errorf("invalid user identifier: %w", domain.ErrInvalidData)
}
if new.Identifier == "all" { // the all user identifier collides with the rest api routes
return fmt.Errorf("reserved user identifier")
if new.Identifier == "all" { // the 'all' user identifier collides with the rest api routes
return fmt.Errorf("reserved user identifier: %w", domain.ErrInvalidData)
}
if new.Identifier == "new" { // the new user identifier collides with the rest api routes
return fmt.Errorf("reserved user identifier")
if new.Identifier == "new" { // the 'new' user identifier collides with the rest api routes
return fmt.Errorf("reserved user identifier: %w", domain.ErrInvalidData)
}
if new.Identifier == "id" { // the 'id' user identifier collides with the rest api routes
return fmt.Errorf("reserved user identifier: %w", domain.ErrInvalidData)
}
if new.Identifier == domain.CtxSystemAdminId || new.Identifier == domain.CtxUnknownUserId {
return fmt.Errorf("reserved user identifier: %w", domain.ErrInvalidData)
}
if new.Source != domain.UserSourceDatabase {
return fmt.Errorf("invalid user source: %s, only %s is allowed", new.Source, domain.UserSourceDatabase)
return fmt.Errorf("invalid user source: %s, only %s is allowed: %w",
new.Source, domain.UserSourceDatabase, domain.ErrInvalidData)
}
if string(new.Password) == "" {
return fmt.Errorf("invalid password")
return fmt.Errorf("invalid password: %w", domain.ErrInvalidData)
}
return nil
@ -362,15 +371,15 @@ func (m Manager) validateDeletion(ctx context.Context, del *domain.User) error {
currentUser := domain.GetUserInfo(ctx)
if !currentUser.IsAdmin {
return fmt.Errorf("insufficient permissions")
return domain.ErrNoPermission
}
if err := del.DeleteAllowed(); err != nil {
return fmt.Errorf("no access: %w", err)
return errors.Join(fmt.Errorf("no access: %w", err), domain.ErrInvalidData)
}
if currentUser.Id == del.Identifier {
return fmt.Errorf("cannot delete own user")
return fmt.Errorf("cannot delete own user: %w", domain.ErrInvalidData)
}
return nil
@ -380,7 +389,7 @@ func (m Manager) validateApiChange(ctx context.Context, user *domain.User) error
currentUser := domain.GetUserInfo(ctx)
if currentUser.Id != user.Identifier {
return fmt.Errorf("cannot change API access of user")
return fmt.Errorf("cannot change API access of user: %w", domain.ErrNoPermission)
}
return nil

View File

@ -39,6 +39,7 @@ type Config struct {
ExpiryCheckInterval time.Duration `yaml:"expiry_check_interval"`
RulePrioOffset int `yaml:"rule_prio_offset"`
RouteTableOffset int `yaml:"route_table_offset"`
ApiAdminOnly bool `yaml:"api_admin_only"` // if true, only admin users can access the API
} `yaml:"advanced"`
Statistics struct {

View File

@ -94,7 +94,7 @@ func ValidateUserAccessRights(ctx context.Context, requiredUser UserIdentifier)
}
logrus.Warnf("insufficient permissions for %s (want %s), stack: %s", sessionUser.Id, requiredUser, GetStackTrace())
return fmt.Errorf("insufficient permissions")
return ErrNoPermission
}
// ValidateAdminAccessRights checks if the current user has admin access rights.
@ -106,5 +106,5 @@ func ValidateAdminAccessRights(ctx context.Context) error {
}
logrus.Warnf("insufficient admin permissions for %s, stack: %s", sessionUser.Id, GetStackTrace())
return fmt.Errorf("insufficient permissions")
return ErrNoPermission
}