mirror of
https://github.com/h44z/wg-portal
synced 2025-06-26 18:16:21 +00:00
tmp
This commit is contained in:
parent
04f87bf37a
commit
61ca0fd67a
@ -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)
|
||||
|
||||
|
@ -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": {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user