mirror of
https://github.com/h44z/wg-portal
synced 2025-06-26 18:16:21 +00:00
first provisioning endpoints
This commit is contained in:
parent
e35dff6538
commit
38838eb4ce
@ -108,10 +108,18 @@ func main() {
|
||||
apiV1BackendUsers := backendV1.NewUserService(cfg, userManager)
|
||||
apiV1BackendPeers := backendV1.NewPeerService(cfg, wireGuardManager, userManager)
|
||||
apiV1BackendInterfaces := backendV1.NewInterfaceService(cfg, wireGuardManager)
|
||||
apiV1BackendProvisioning := backendV1.NewProvisioningService(cfg, userManager, wireGuardManager, cfgFileManager)
|
||||
apiV1EndpointUsers := handlersV1.NewUserEndpoint(apiV1BackendUsers)
|
||||
apiV1EndpointPeers := handlersV1.NewPeerEndpoint(apiV1BackendPeers)
|
||||
apiV1EndpointInterfaces := handlersV1.NewInterfaceEndpoint(apiV1BackendInterfaces)
|
||||
apiV1 := handlersV1.NewRestApi(userManager, apiV1EndpointUsers, apiV1EndpointPeers, apiV1EndpointInterfaces)
|
||||
apiV1EndpointProvisioning := handlersV1.NewProvisioningEndpoint(apiV1BackendProvisioning)
|
||||
apiV1 := handlersV1.NewRestApi(
|
||||
userManager,
|
||||
apiV1EndpointUsers,
|
||||
apiV1EndpointPeers,
|
||||
apiV1EndpointInterfaces,
|
||||
apiV1EndpointProvisioning,
|
||||
)
|
||||
|
||||
webSrv, err := core.NewServer(cfg, apiFrontend, apiV1)
|
||||
internal.AssertNoError(err)
|
||||
|
@ -698,6 +698,30 @@ func (r *SqlRepo) GetUser(ctx context.Context, id domain.UserIdentifier) (*domai
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *SqlRepo) GetUserByEmail(ctx context.Context, email string) (*domain.User, error) {
|
||||
var users []domain.User
|
||||
|
||||
err := r.db.WithContext(ctx).Where("email = ?", email).Find(&users).Error
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, domain.ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
return nil, domain.ErrNotFound
|
||||
}
|
||||
|
||||
if len(users) > 1 {
|
||||
return nil, fmt.Errorf("found multiple users with email %s: %w", email, domain.ErrNotUnique)
|
||||
}
|
||||
|
||||
user := users[0]
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *SqlRepo) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
||||
var users []domain.User
|
||||
|
||||
|
@ -665,6 +665,208 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/provisioning/data/peer-config": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BasicAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Normal users can only access their own record. Admins can access all records.",
|
||||
"produces": [
|
||||
"text/plain",
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Provisioning"
|
||||
],
|
||||
"summary": "Get the peer configuration in wg-quick format.",
|
||||
"operationId": "provisioning_handlePeerConfigGet",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "The peer identifier (public key) that should be queried.",
|
||||
"name": "PeerId",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The WireGuard configuration file",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/provisioning/data/peer-qr": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BasicAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Normal users can only access their own record. Admins can access all records.",
|
||||
"produces": [
|
||||
"image/png",
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Provisioning"
|
||||
],
|
||||
"summary": "Get the peer configuration as QR code.",
|
||||
"operationId": "provisioning_handlePeerQrGet",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "The peer identifier (public key) that should be queried.",
|
||||
"name": "PeerId",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The WireGuard configuration QR code",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/provisioning/data/user-info": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BasicAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Normal users can only access their own record. Admins can access all records.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Provisioning"
|
||||
],
|
||||
"summary": "Get information about all peer records for a given user.",
|
||||
"operationId": "provisioning_handleUserInfoGet",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "The user identifier that should be queried. If not set, the authenticated user is used.",
|
||||
"name": "UserId",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "The email address that should be queried. If UserId is set, this is ignored.",
|
||||
"name": "Email",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.UserInformation"
|
||||
}
|
||||
},
|
||||
"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/all": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -1568,6 +1770,60 @@
|
||||
"example": "db"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.UserInformation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"PeerCount": {
|
||||
"type": "integer",
|
||||
"example": 2
|
||||
},
|
||||
"Peers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.UserInformationPeer"
|
||||
}
|
||||
},
|
||||
"UserIdentifier": {
|
||||
"type": "string",
|
||||
"example": "uid-1234567"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.UserInformationPeer": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"DisplayName": {
|
||||
"description": "DisplayName is a user-defined description of the peer.",
|
||||
"type": "string",
|
||||
"example": "My iPhone"
|
||||
},
|
||||
"Identifier": {
|
||||
"description": "Identifier is the unique identifier of the peer. It equals the public key of the peer.",
|
||||
"type": "string",
|
||||
"example": "peer-1234567"
|
||||
},
|
||||
"InterfaceIdentifier": {
|
||||
"description": "InterfaceIdentifier is the unique identifier of the WireGuard Portal device the peer is connected to.",
|
||||
"type": "string",
|
||||
"example": "wg0"
|
||||
},
|
||||
"IpAddresses": {
|
||||
"description": "IPAddresses is a list of IP addresses in CIDR format assigned to the peer.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"10.11.12.2/24"
|
||||
]
|
||||
},
|
||||
"IsDisabled": {
|
||||
"description": "IsDisabled is a flag that specifies if the peer is enabled or not. Disabled peers are not able to connect.",
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
|
@ -475,6 +475,49 @@ definitions:
|
||||
- Identifier
|
||||
- IsAdmin
|
||||
type: object
|
||||
models.UserInformation:
|
||||
properties:
|
||||
PeerCount:
|
||||
example: 2
|
||||
type: integer
|
||||
Peers:
|
||||
items:
|
||||
$ref: '#/definitions/models.UserInformationPeer'
|
||||
type: array
|
||||
UserIdentifier:
|
||||
example: uid-1234567
|
||||
type: string
|
||||
type: object
|
||||
models.UserInformationPeer:
|
||||
properties:
|
||||
DisplayName:
|
||||
description: DisplayName is a user-defined description of the peer.
|
||||
example: My iPhone
|
||||
type: string
|
||||
Identifier:
|
||||
description: Identifier is the unique identifier of the peer. It equals the
|
||||
public key of the peer.
|
||||
example: peer-1234567
|
||||
type: string
|
||||
InterfaceIdentifier:
|
||||
description: InterfaceIdentifier is the unique identifier of the WireGuard
|
||||
Portal device the peer is connected to.
|
||||
example: wg0
|
||||
type: string
|
||||
IpAddresses:
|
||||
description: IPAddresses is a list of IP addresses in CIDR format assigned
|
||||
to the peer.
|
||||
example:
|
||||
- 10.11.12.2/24
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
IsDisabled:
|
||||
description: IsDisabled is a flag that specifies if the peer is enabled or
|
||||
not. Disabled peers are not able to connect.
|
||||
example: true
|
||||
type: boolean
|
||||
type: object
|
||||
info:
|
||||
contact:
|
||||
name: WireGuard Portal Project
|
||||
@ -908,6 +951,142 @@ paths:
|
||||
summary: Create a new peer record.
|
||||
tags:
|
||||
- Peers
|
||||
/provisioning/data/peer-config:
|
||||
get:
|
||||
description: Normal users can only access their own record. Admins can access
|
||||
all records.
|
||||
operationId: provisioning_handlePeerConfigGet
|
||||
parameters:
|
||||
- description: The peer identifier (public key) that should be queried.
|
||||
in: query
|
||||
name: PeerId
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- text/plain
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The WireGuard configuration file
|
||||
schema:
|
||||
type: string
|
||||
"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: Get the peer configuration in wg-quick format.
|
||||
tags:
|
||||
- Provisioning
|
||||
/provisioning/data/peer-qr:
|
||||
get:
|
||||
description: Normal users can only access their own record. Admins can access
|
||||
all records.
|
||||
operationId: provisioning_handlePeerQrGet
|
||||
parameters:
|
||||
- description: The peer identifier (public key) that should be queried.
|
||||
in: query
|
||||
name: PeerId
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- image/png
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The WireGuard configuration QR code
|
||||
schema:
|
||||
type: file
|
||||
"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: Get the peer configuration as QR code.
|
||||
tags:
|
||||
- Provisioning
|
||||
/provisioning/data/user-info:
|
||||
get:
|
||||
description: Normal users can only access their own record. Admins can access
|
||||
all records.
|
||||
operationId: provisioning_handleUserInfoGet
|
||||
parameters:
|
||||
- description: The user identifier that should be queried. If not set, the authenticated
|
||||
user is used.
|
||||
in: query
|
||||
name: UserId
|
||||
type: string
|
||||
- description: The email address that should be queried. If UserId is set, this
|
||||
is ignored.
|
||||
in: query
|
||||
name: Email
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.UserInformation'
|
||||
"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: Get information about all peer records for a given user.
|
||||
tags:
|
||||
- Provisioning
|
||||
/user/all:
|
||||
get:
|
||||
operationId: users_handleAllGet
|
||||
|
@ -1,12 +1,13 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"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"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type peerEndpoint struct {
|
||||
@ -57,7 +58,8 @@ func (e peerEndpoint) handleAllGet() gin.HandlerFunc {
|
||||
|
||||
_, peers, err := e.app.GetInterfaceAndPeers(ctx, domain.InterfaceIdentifier(interfaceId))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
c.JSON(http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@ -88,7 +90,8 @@ func (e peerEndpoint) handleSingleGet() gin.HandlerFunc {
|
||||
|
||||
peer, err := e.app.GetPeer(ctx, domain.PeerIdentifier(peerId))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
c.JSON(http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@ -119,7 +122,8 @@ func (e peerEndpoint) handlePrepareGet() gin.HandlerFunc {
|
||||
|
||||
peer, err := e.app.PreparePeer(ctx, domain.InterfaceIdentifier(interfaceId))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
c.JSON(http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@ -163,7 +167,8 @@ func (e peerEndpoint) handleCreatePost() gin.HandlerFunc {
|
||||
|
||||
newPeer, err := e.app.CreatePeer(ctx, model.NewDomainPeer(&p))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
c.JSON(http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@ -200,9 +205,11 @@ func (e peerEndpoint) handleCreateMultiplePost() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
newPeers, err := e.app.CreateMultiplePeers(ctx, domain.InterfaceIdentifier(interfaceId), model.NewDomainPeerCreationRequest(&req))
|
||||
newPeers, err := e.app.CreateMultiplePeers(ctx, domain.InterfaceIdentifier(interfaceId),
|
||||
model.NewDomainPeerCreationRequest(&req))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
c.JSON(http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@ -246,7 +253,8 @@ func (e peerEndpoint) handleUpdatePut() gin.HandlerFunc {
|
||||
|
||||
updatedPeer, err := e.app.UpdatePeer(ctx, model.NewDomainPeer(&p))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
c.JSON(http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@ -277,7 +285,8 @@ func (e peerEndpoint) handleDelete() gin.HandlerFunc {
|
||||
|
||||
err := e.app.DeletePeer(ctx, domain.PeerIdentifier(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
c.JSON(http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@ -333,9 +342,10 @@ func (e peerEndpoint) handleConfigGet() gin.HandlerFunc {
|
||||
// @ID peers_handleQrCodeGet
|
||||
// @Tags Peer
|
||||
// @Summary Get peer configuration as qr code.
|
||||
// @Produce png
|
||||
// @Produce json
|
||||
// @Param id path string true "The peer identifier"
|
||||
// @Success 200 {object} string
|
||||
// @Success 200 {object} file
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /peer/config-qr/{id} [get]
|
||||
@ -403,7 +413,8 @@ func (e peerEndpoint) handleEmailPost() gin.HandlerFunc {
|
||||
}
|
||||
err = e.app.SendPeerEmail(ctx, req.LinkOnly, peerIds...)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
c.JSON(http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@ -434,7 +445,8 @@ func (e peerEndpoint) handleStatsGet() gin.HandlerFunc {
|
||||
|
||||
stats, err := e.app.GetPeerStats(ctx, domain.InterfaceIdentifier(interfaceId))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
c.JSON(http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
|
130
internal/app/api/v1/backend/provisioning_service.go
Normal file
130
internal/app/api/v1/backend/provisioning_service.go
Normal file
@ -0,0 +1,130 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
type ProvisioningServiceUserManagerRepo interface {
|
||||
GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
|
||||
GetUserByEmail(ctx context.Context, email string) (*domain.User, error)
|
||||
}
|
||||
|
||||
type ProvisioningServicePeerManagerRepo interface {
|
||||
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
|
||||
GetUserPeers(context.Context, domain.UserIdentifier) ([]domain.Peer, error)
|
||||
}
|
||||
|
||||
type ProvisioningServiceConfigFileManagerRepo interface {
|
||||
GetPeerConfig(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error)
|
||||
GetPeerConfigQrCode(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error)
|
||||
}
|
||||
|
||||
type ProvisioningService struct {
|
||||
cfg *config.Config
|
||||
|
||||
users ProvisioningServiceUserManagerRepo
|
||||
peers ProvisioningServicePeerManagerRepo
|
||||
configFiles ProvisioningServiceConfigFileManagerRepo
|
||||
}
|
||||
|
||||
func NewProvisioningService(
|
||||
cfg *config.Config,
|
||||
users ProvisioningServiceUserManagerRepo,
|
||||
peers ProvisioningServicePeerManagerRepo,
|
||||
configFiles ProvisioningServiceConfigFileManagerRepo,
|
||||
) *ProvisioningService {
|
||||
return &ProvisioningService{
|
||||
cfg: cfg,
|
||||
|
||||
users: users,
|
||||
peers: peers,
|
||||
configFiles: configFiles,
|
||||
}
|
||||
}
|
||||
|
||||
func (p ProvisioningService) GetUserAndPeers(
|
||||
ctx context.Context,
|
||||
userId domain.UserIdentifier,
|
||||
email string,
|
||||
) (*domain.User, []domain.Peer, error) {
|
||||
// first fetch user
|
||||
var user *domain.User
|
||||
switch {
|
||||
case userId != "":
|
||||
u, err := p.users.GetUser(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
user = u
|
||||
case email != "":
|
||||
u, err := p.users.GetUserByEmail(ctx, email)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
user = u
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("either UserId or Email must be set: %w", domain.ErrInvalidData)
|
||||
}
|
||||
|
||||
if err := domain.ValidateUserAccessRights(ctx, user.Identifier); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
peers, err := p.peers.GetUserPeers(ctx, user.Identifier)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return user, peers, nil
|
||||
}
|
||||
|
||||
func (p ProvisioningService) GetPeerConfig(ctx context.Context, peerId domain.PeerIdentifier) ([]byte, error) {
|
||||
peer, err := p.peers.GetPeer(ctx, peerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := domain.ValidateUserAccessRights(ctx, peer.UserIdentifier); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peerCfgReader, err := p.configFiles.GetPeerConfig(ctx, peer.Identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peerCfgData, err := io.ReadAll(peerCfgReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return peerCfgData, nil
|
||||
}
|
||||
|
||||
func (p ProvisioningService) GetPeerQrPng(ctx context.Context, peerId domain.PeerIdentifier) ([]byte, error) {
|
||||
peer, err := p.peers.GetPeer(ctx, peerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := domain.ValidateUserAccessRights(ctx, peer.UserIdentifier); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peerCfgQrReader, err := p.configFiles.GetPeerConfigQrCode(ctx, peer.Identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peerCfgQrData, err := io.ReadAll(peerCfgQrReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return peerCfgQrData, nil
|
||||
}
|
155
internal/app/api/v1/handlers/endpoint_provisioning.go
Normal file
155
internal/app/api/v1/handlers/endpoint_provisioning.go
Normal file
@ -0,0 +1,155 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/h44z/wg-portal/internal/app/api/v1/models"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
type ProvisioningEndpointProvisioningService interface {
|
||||
GetUserAndPeers(ctx context.Context, userId domain.UserIdentifier, email string) (
|
||||
*domain.User,
|
||||
[]domain.Peer,
|
||||
error,
|
||||
)
|
||||
GetPeerConfig(ctx context.Context, peerId domain.PeerIdentifier) ([]byte, error)
|
||||
GetPeerQrPng(ctx context.Context, peerId domain.PeerIdentifier) ([]byte, error)
|
||||
}
|
||||
|
||||
type ProvisioningEndpoint struct {
|
||||
provisioning ProvisioningEndpointProvisioningService
|
||||
}
|
||||
|
||||
func NewProvisioningEndpoint(provisioning ProvisioningEndpointProvisioningService) *ProvisioningEndpoint {
|
||||
return &ProvisioningEndpoint{
|
||||
provisioning: provisioning,
|
||||
}
|
||||
}
|
||||
|
||||
func (e ProvisioningEndpoint) GetName() string {
|
||||
return "ProvisioningEndpoint"
|
||||
}
|
||||
|
||||
func (e ProvisioningEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
|
||||
apiGroup := g.Group("/provisioning", authenticator.LoggedIn())
|
||||
|
||||
apiGroup.GET("/data/user-info", authenticator.LoggedIn(), e.handleUserInfoGet())
|
||||
apiGroup.GET("/data/peer-config", authenticator.LoggedIn(), e.handlePeerConfigGet())
|
||||
apiGroup.GET("/data/peer-qr", authenticator.LoggedIn(), e.handlePeerQrGet())
|
||||
}
|
||||
|
||||
// handleUserInfoGet returns a gorm Handler function.
|
||||
//
|
||||
// @ID provisioning_handleUserInfoGet
|
||||
// @Tags Provisioning
|
||||
// @Summary Get information about all peer records for a given user.
|
||||
// @Description Normal users can only access their own record. Admins can access all records.
|
||||
// @Param UserId query string false "The user identifier that should be queried. If not set, the authenticated user is used."
|
||||
// @Param Email query string false "The email address that should be queried. If UserId is set, this is ignored."
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.UserInformation
|
||||
// @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 /provisioning/data/user-info [get]
|
||||
// @Security BasicAuth
|
||||
func (e ProvisioningEndpoint) handleUserInfoGet() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
id := strings.TrimSpace(c.Query("UserId"))
|
||||
email := strings.TrimSpace(c.Query("Email"))
|
||||
|
||||
if id == "" && email == "" {
|
||||
id = string(domain.GetUserInfo(ctx).Id)
|
||||
}
|
||||
|
||||
user, peers, err := e.provisioning.GetUserAndPeers(ctx, domain.UserIdentifier(id), email)
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.NewUserInformation(user, peers))
|
||||
}
|
||||
}
|
||||
|
||||
// handlePeerConfigGet returns a gorm Handler function.
|
||||
//
|
||||
// @ID provisioning_handlePeerConfigGet
|
||||
// @Tags Provisioning
|
||||
// @Summary Get the peer configuration in wg-quick format.
|
||||
// @Description Normal users can only access their own record. Admins can access all records.
|
||||
// @Param PeerId query string true "The peer identifier (public key) that should be queried."
|
||||
// @Produce plain
|
||||
// @Produce json
|
||||
// @Success 200 {string} string "The WireGuard configuration file"
|
||||
// @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 /provisioning/data/peer-config [get]
|
||||
// @Security BasicAuth
|
||||
func (e ProvisioningEndpoint) handlePeerConfigGet() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
id := strings.TrimSpace(c.Query("PeerId"))
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing peer id"})
|
||||
return
|
||||
}
|
||||
|
||||
peerConfig, err := e.provisioning.GetPeerConfig(ctx, domain.PeerIdentifier(id))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.Data(http.StatusOK, "text/plain", peerConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// handlePeerQrGet returns a gorm Handler function.
|
||||
//
|
||||
// @ID provisioning_handlePeerQrGet
|
||||
// @Tags Provisioning
|
||||
// @Summary Get the peer configuration as QR code.
|
||||
// @Description Normal users can only access their own record. Admins can access all records.
|
||||
// @Param PeerId query string true "The peer identifier (public key) that should be queried."
|
||||
// @Produce png
|
||||
// @Produce json
|
||||
// @Success 200 {file} binary "The WireGuard configuration QR code"
|
||||
// @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 /provisioning/data/peer-qr [get]
|
||||
// @Security BasicAuth
|
||||
func (e ProvisioningEndpoint) handlePeerQrGet() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
id := strings.TrimSpace(c.Query("PeerId"))
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing peer id"})
|
||||
return
|
||||
}
|
||||
|
||||
peerConfigQrCode, err := e.provisioning.GetPeerQrPng(ctx, domain.PeerIdentifier(id))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.Data(http.StatusOK, "image/png", peerConfigQrCode)
|
||||
}
|
||||
}
|
58
internal/app/api/v1/models/models_provisioning.go
Normal file
58
internal/app/api/v1/models/models_provisioning.go
Normal file
@ -0,0 +1,58 @@
|
||||
package models
|
||||
|
||||
import "github.com/h44z/wg-portal/internal/domain"
|
||||
|
||||
// UserInformation represents the information about a user and its linked peers.
|
||||
type UserInformation struct {
|
||||
UserIdentifier string `json:"UserIdentifier" example:"uid-1234567"`
|
||||
PeerCount int `json:"PeerCount" example:"2"`
|
||||
Peers []UserInformationPeer `json:"Peers"`
|
||||
}
|
||||
|
||||
// UserInformationPeer represents the information about a peer.
|
||||
type UserInformationPeer struct {
|
||||
// Identifier is the unique identifier of the peer. It equals the public key of the peer.
|
||||
Identifier string `json:"Identifier" example:"peer-1234567"`
|
||||
// DisplayName is a user-defined description of the peer.
|
||||
DisplayName string `json:"DisplayName" example:"My iPhone"`
|
||||
// IPAddresses is a list of IP addresses in CIDR format assigned to the peer.
|
||||
IpAddresses []string `json:"IpAddresses" example:"10.11.12.2/24"`
|
||||
// IsDisabled is a flag that specifies if the peer is enabled or not. Disabled peers are not able to connect.
|
||||
IsDisabled bool `json:"IsDisabled,omitempty" example:"true"`
|
||||
|
||||
// InterfaceIdentifier is the unique identifier of the WireGuard Portal device the peer is connected to.
|
||||
InterfaceIdentifier string `json:"InterfaceIdentifier" example:"wg0"`
|
||||
}
|
||||
|
||||
func NewUserInformation(user *domain.User, peers []domain.Peer) *UserInformation {
|
||||
if user == nil {
|
||||
return &UserInformation{}
|
||||
}
|
||||
|
||||
ui := &UserInformation{
|
||||
UserIdentifier: string(user.Identifier),
|
||||
PeerCount: len(peers),
|
||||
}
|
||||
|
||||
for _, peer := range peers {
|
||||
ui.Peers = append(ui.Peers, NewUserInformationPeer(peer))
|
||||
}
|
||||
|
||||
if len(ui.Peers) == 0 {
|
||||
ui.Peers = []UserInformationPeer{} // Ensure that the JSON output is an empty array instead of null.
|
||||
}
|
||||
|
||||
return ui
|
||||
}
|
||||
|
||||
func NewUserInformationPeer(peer domain.Peer) UserInformationPeer {
|
||||
up := UserInformationPeer{
|
||||
Identifier: string(peer.Identifier),
|
||||
DisplayName: peer.DisplayName,
|
||||
IpAddresses: domain.CidrsToStringSlice(peer.Interface.Addresses),
|
||||
IsDisabled: peer.IsDisabled(),
|
||||
InterfaceIdentifier: string(peer.InterfaceIdentifier),
|
||||
}
|
||||
|
||||
return up
|
||||
}
|
@ -2,11 +2,13 @@ package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
type UserDatabaseRepo interface {
|
||||
GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
|
||||
GetUserByEmail(ctx context.Context, email string) (*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
|
||||
|
@ -111,6 +111,24 @@ func (m Manager) GetUser(ctx context.Context, id domain.UserIdentifier) (*domain
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (m Manager) GetUserByEmail(ctx context.Context, email string) (*domain.User, error) {
|
||||
|
||||
user, err := m.users.GetUserByEmail(ctx, email)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load user for email %s: %w", email, err)
|
||||
}
|
||||
|
||||
if err := domain.ValidateUserAccessRights(ctx, user.Identifier); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peers, _ := m.peers.GetUserPeers(ctx, user.Identifier) // ignore error, list will be empty in error case
|
||||
|
||||
user.LinkedPeerCount = len(peers)
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (m Manager) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return nil, err
|
||||
|
Loading…
Reference in New Issue
Block a user