mirror of
https://github.com/h44z/wg-portal
synced 2025-06-26 18:16:21 +00:00
CRUD operations for interfaces, peers and users
This commit is contained in:
parent
61ca0fd67a
commit
e35dff6538
@ -106,8 +106,12 @@ func main() {
|
||||
apiFrontend := handlersV0.NewRestApi(cfg, backend)
|
||||
|
||||
apiV1BackendUsers := backendV1.NewUserService(cfg, userManager)
|
||||
apiV1BackendPeers := backendV1.NewPeerService(cfg, wireGuardManager, userManager)
|
||||
apiV1BackendInterfaces := backendV1.NewInterfaceService(cfg, wireGuardManager)
|
||||
apiV1EndpointUsers := handlersV1.NewUserEndpoint(apiV1BackendUsers)
|
||||
apiV1 := handlersV1.NewRestApi(userManager, apiV1EndpointUsers)
|
||||
apiV1EndpointPeers := handlersV1.NewPeerEndpoint(apiV1BackendPeers)
|
||||
apiV1EndpointInterfaces := handlersV1.NewInterfaceEndpoint(apiV1BackendInterfaces)
|
||||
apiV1 := handlersV1.NewRestApi(userManager, apiV1EndpointUsers, apiV1EndpointPeers, apiV1EndpointInterfaces)
|
||||
|
||||
webSrv, err := core.NewServer(cfg, apiFrontend, apiV1)
|
||||
internal.AssertNoError(err)
|
||||
|
@ -90,7 +90,7 @@ const currentYear = ref(new Date().getFullYear())
|
||||
href="#" role="button">{{ auth.User.Firstname }} {{ auth.User.Lastname }}</a>
|
||||
<div class="dropdown-menu">
|
||||
<RouterLink :to="{ name: 'profile' }" class="dropdown-item"><i class="fas fa-user"></i> {{ $t('menu.profile') }}</RouterLink>
|
||||
<RouterLink :to="{ name: 'settings' }" class="dropdown-item"><i class="fas fa-gears"></i> {{ $t('menu.settings') }}</RouterLink>
|
||||
<RouterLink :to="{ name: 'settings' }" class="dropdown-item" v-if="auth.IsAdmin || !settings.Setting('ApiAdminOnly')"><i class="fas fa-gears"></i> {{ $t('menu.settings') }}</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') }}</a>
|
||||
</div>
|
||||
|
@ -7,8 +7,11 @@ import PeerEditModal from "@/components/PeerEditModal.vue";
|
||||
import { settingsStore } from "@/stores/settings";
|
||||
import { humanFileSize } from "@/helpers/utils";
|
||||
import {RouterLink} from "vue-router";
|
||||
import {authStore} from "../stores/auth";
|
||||
|
||||
const profile = profileStore()
|
||||
const settings = settingsStore()
|
||||
const auth = authStore()
|
||||
|
||||
onMounted(async () => {
|
||||
await profile.LoadUser()
|
||||
@ -23,50 +26,52 @@ onMounted(async () => {
|
||||
|
||||
<p class="lead">{{ $t('settings.abstract') }}</p>
|
||||
|
||||
<div class="bg-light p-5" v-if="profile.user.ApiToken">
|
||||
<h2 class="display-7">{{ $t('settings.api.headline') }}</h2>
|
||||
<p class="lead">{{ $t('settings.api.abstract') }}</p>
|
||||
<hr class="my-4">
|
||||
<p>{{ $t('settings.api.active-description') }}</p>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('settings.api.user-label') }}</label>
|
||||
<input v-model="profile.user.Identifier" class="form-control" :placeholder="$t('settings.api.user-placeholder')" type="text" readonly>
|
||||
<div v-if="auth.IsAdmin || !settings.Setting('ApiAdminOnly')">
|
||||
<div class="bg-light p-5" v-if="profile.user.ApiToken">
|
||||
<h2 class="display-7">{{ $t('settings.api.headline') }}</h2>
|
||||
<p class="lead">{{ $t('settings.api.abstract') }}</p>
|
||||
<hr class="my-4">
|
||||
<p>{{ $t('settings.api.active-description') }}</p>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('settings.api.user-label') }}</label>
|
||||
<input v-model="profile.user.Identifier" class="form-control" :placeholder="$t('settings.api.user-placeholder')" type="text" readonly>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('settings.api.token-label') }}</label>
|
||||
<input v-model="profile.user.ApiToken" class="form-control" :placeholder="$t('settings.api.token-placeholder')" type="text" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('settings.api.token-label') }}</label>
|
||||
<input v-model="profile.user.ApiToken" class="form-control" :placeholder="$t('settings.api.token-placeholder')" type="text" readonly>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="form-group">
|
||||
<p class="form-label mt-4">{{ $t('settings.api.token-created-label') }} {{profile.user.ApiTokenCreated}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<div class="col-6">
|
||||
<button class="input-group-text btn btn-primary" :title="$t('settings.api.button-disable-title')" @click.prevent="profile.disableApi()" :disabled="profile.isFetching">
|
||||
<i class="fa-solid fa-minus-circle"></i> {{ $t('settings.api.button-disable-text') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<a href="/api/v1/doc.html" target="_blank" :alt="$t('settings.api.api-link')">{{ $t('settings.api.api-link') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="form-group">
|
||||
<p class="form-label mt-4">{{ $t('settings.api.token-created-label') }} {{profile.user.ApiTokenCreated}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-light p-5" v-else>
|
||||
<h2 class="display-7">{{ $t('settings.api.headline') }}</h2>
|
||||
<p class="lead">{{ $t('settings.api.abstract') }}</p>
|
||||
<hr class="my-4">
|
||||
<p>{{ $t('settings.api.inactive-description') }}</p>
|
||||
<button class="input-group-text btn btn-primary" :title="$t('settings.api.button-enable-title')" @click.prevent="profile.enableApi()" :disabled="profile.isFetching">
|
||||
<i class="fa-solid fa-plus-circle"></i> {{ $t('settings.api.button-enable-text') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<div class="col-6">
|
||||
<button class="input-group-text btn btn-primary" :title="$t('settings.api.button-disable-title')" @click.prevent="profile.disableApi()" :disabled="profile.isFetching">
|
||||
<i class="fa-solid fa-minus-circle"></i> {{ $t('settings.api.button-disable-text') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<a href="/api/v1/doc.html" target="_blank" :alt="$t('settings.api.api-link')">{{ $t('settings.api.api-link') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-light p-5" v-else>
|
||||
<h2 class="display-7">{{ $t('settings.api.headline') }}</h2>
|
||||
<p class="lead">{{ $t('settings.api.abstract') }}</p>
|
||||
<hr class="my-4">
|
||||
<p>{{ $t('settings.api.inactive-description') }}</p>
|
||||
<button class="input-group-text btn btn-primary" :title="$t('settings.api.button-enable-title')" @click.prevent="profile.enableApi()" :disabled="profile.isFetching">
|
||||
<i class="fa-solid fa-plus-circle"></i> {{ $t('settings.api.button-enable-text') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1298,7 +1298,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/{id}/api/enable": {
|
||||
"/user/{id}/api/disable": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
@ -1330,6 +1330,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/{id}/api/enable": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Enable the REST API for the given user.",
|
||||
"operationId": "users_handleApiEnablePost",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.User"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/{id}/peers": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
@ -1217,7 +1217,7 @@ paths:
|
||||
summary: Update the user record.
|
||||
tags:
|
||||
- Users
|
||||
/user/{id}/api/enable:
|
||||
/user/{id}/api/disable:
|
||||
post:
|
||||
operationId: users_handleApiDisablePost
|
||||
produces:
|
||||
@ -1238,6 +1238,27 @@ paths:
|
||||
summary: Disable the REST API for the given user.
|
||||
tags:
|
||||
- Users
|
||||
/user/{id}/api/enable:
|
||||
post:
|
||||
operationId: users_handleApiEnablePost
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/model.User'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
summary: Enable the REST API for the given user.
|
||||
tags:
|
||||
- Users
|
||||
/user/{id}/peers:
|
||||
get:
|
||||
operationId: users_handlePeersGet
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,35 @@
|
||||
basePath: /api/v1
|
||||
definitions:
|
||||
models.ConfigOption-array_string:
|
||||
properties:
|
||||
Overridable:
|
||||
type: boolean
|
||||
Value:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
models.ConfigOption-int:
|
||||
properties:
|
||||
Overridable:
|
||||
type: boolean
|
||||
Value:
|
||||
type: integer
|
||||
type: object
|
||||
models.ConfigOption-string:
|
||||
properties:
|
||||
Overridable:
|
||||
type: boolean
|
||||
Value:
|
||||
type: string
|
||||
type: object
|
||||
models.ConfigOption-uint32:
|
||||
properties:
|
||||
Overridable:
|
||||
type: boolean
|
||||
Value:
|
||||
type: integer
|
||||
type: object
|
||||
models.Error:
|
||||
properties:
|
||||
Code:
|
||||
@ -12,78 +42,872 @@ definitions:
|
||||
description: Error message.
|
||||
type: string
|
||||
type: object
|
||||
models.ExpiryDate:
|
||||
properties:
|
||||
time.Time:
|
||||
type: string
|
||||
type: object
|
||||
models.Interface:
|
||||
properties:
|
||||
Addresses:
|
||||
description: Addresses is a list of IP addresses (in CIDR format) that are
|
||||
assigned to the interface.
|
||||
example:
|
||||
- 10.11.12.1/24
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
Disabled:
|
||||
description: Disabled is a flag that specifies if the interface is enabled
|
||||
(up) or not (down). Disabled interfaces are not able to accept connections.
|
||||
example: false
|
||||
type: boolean
|
||||
DisabledReason:
|
||||
description: DisabledReason is the reason why the interface has been disabled.
|
||||
example: This is a reason why the interface has been disabled.
|
||||
type: string
|
||||
DisplayName:
|
||||
description: DisplayName is a nice display name / description for the interface.
|
||||
example: My Interface
|
||||
maxLength: 64
|
||||
type: string
|
||||
Dns:
|
||||
description: Dns is a list of DNS servers that should be set if the interface
|
||||
is up.
|
||||
example:
|
||||
- 1.1.1.1
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
DnsSearch:
|
||||
description: DnsSearch is the dns search option string that should be set
|
||||
if the interface is up, will be appended to Dns servers.
|
||||
example:
|
||||
- wg.local
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
EnabledPeers:
|
||||
description: EnabledPeers is the number of enabled peers for this interface.
|
||||
Only enabled peers are able to connect.
|
||||
readOnly: true
|
||||
type: integer
|
||||
FirewallMark:
|
||||
description: FirewallMark is an optional firewall mark which is used to handle
|
||||
interface traffic.
|
||||
type: integer
|
||||
Identifier:
|
||||
description: Identifier is the unique identifier of the interface. It is always
|
||||
equal to the device name of the interface.
|
||||
example: wg0
|
||||
type: string
|
||||
ListenPort:
|
||||
description: 'ListenPort is the listening port, for example: 51820. The listening
|
||||
port is only required for server interfaces.'
|
||||
example: 51820
|
||||
maximum: 65535
|
||||
minimum: 1
|
||||
type: integer
|
||||
Mode:
|
||||
description: Mode is the interface type, either 'server', 'client' or 'any'.
|
||||
The mode specifies how WireGuard Portal handles peers for this interface.
|
||||
enum:
|
||||
- server
|
||||
- client
|
||||
- any
|
||||
example: server
|
||||
type: string
|
||||
Mtu:
|
||||
description: Mtu is the device MTU of the interface.
|
||||
example: 1420
|
||||
maximum: 9000
|
||||
minimum: 1
|
||||
type: integer
|
||||
PeerDefAllowedIPs:
|
||||
description: PeerDefAllowedIPs specifies the default allowed IP addresses
|
||||
for a new peer.
|
||||
example:
|
||||
- 10.11.12.0/24
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
PeerDefDns:
|
||||
description: PeerDefDns specifies the default dns servers for a new peer.
|
||||
example:
|
||||
- 8.8.8.8
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
PeerDefDnsSearch:
|
||||
description: PeerDefDnsSearch specifies the default dns search options for
|
||||
a new peer.
|
||||
example:
|
||||
- wg.local
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
PeerDefEndpoint:
|
||||
description: PeerDefEndpoint specifies the default endpoint for a new peer.
|
||||
example: wg.example.com:51820
|
||||
type: string
|
||||
PeerDefFirewallMark:
|
||||
description: PeerDefFirewallMark specifies the default firewall mark for a
|
||||
new peer.
|
||||
type: integer
|
||||
PeerDefMtu:
|
||||
description: PeerDefMtu specifies the default device MTU for a new peer.
|
||||
example: 1420
|
||||
type: integer
|
||||
PeerDefNetwork:
|
||||
description: PeerDefNetwork specifies the default subnets from which new peers
|
||||
will get their IP addresses. The subnet is specified in CIDR format.
|
||||
example:
|
||||
- 10.11.12.0/24
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
PeerDefPersistentKeepalive:
|
||||
description: PeerDefPersistentKeepalive specifies the default persistent keep-alive
|
||||
value in seconds for a new peer.
|
||||
example: 25
|
||||
type: integer
|
||||
PeerDefPostDown:
|
||||
description: PeerDefPostDown specifies the default action that is executed
|
||||
after the device is down for a new peer.
|
||||
type: string
|
||||
PeerDefPostUp:
|
||||
description: PeerDefPostUp specifies the default action that is executed after
|
||||
the device is up for a new peer.
|
||||
type: string
|
||||
PeerDefPreDown:
|
||||
description: PeerDefPreDown specifies the default action that is executed
|
||||
before the device is down for a new peer.
|
||||
type: string
|
||||
PeerDefPreUp:
|
||||
description: PeerDefPreUp specifies the default action that is executed before
|
||||
the device is up for a new peer.
|
||||
type: string
|
||||
PeerDefRoutingTable:
|
||||
description: PeerDefRoutingTable specifies the default routing table for a
|
||||
new peer.
|
||||
type: string
|
||||
PostDown:
|
||||
description: PostDown is an optional action that is executed after the device
|
||||
is down.
|
||||
example: echo 'Interface is down'
|
||||
type: string
|
||||
PostUp:
|
||||
description: PostUp is an optional action that is executed after the device
|
||||
is up.
|
||||
example: iptables -A FORWARD -i %i -j ACCEPT
|
||||
type: string
|
||||
PreDown:
|
||||
description: PreDown is an optional action that is executed before the device
|
||||
is down.
|
||||
example: iptables -D FORWARD -i %i -j ACCEPT
|
||||
type: string
|
||||
PreUp:
|
||||
description: PreUp is an optional action that is executed before the device
|
||||
is up.
|
||||
example: echo 'Interface is up'
|
||||
type: string
|
||||
PrivateKey:
|
||||
description: PrivateKey is the private key of the interface.
|
||||
example: gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE=
|
||||
type: string
|
||||
PublicKey:
|
||||
description: PublicKey is the public key of the server interface. The public
|
||||
key is used by peers to connect to the server.
|
||||
example: HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw=
|
||||
type: string
|
||||
RoutingTable:
|
||||
description: RoutingTable is an optional routing table which is used to route
|
||||
interface traffic.
|
||||
type: string
|
||||
SaveConfig:
|
||||
description: SaveConfig is a flag that specifies if the configuration should
|
||||
be saved to the configuration file (wgX.conf in wg-quick format).
|
||||
example: false
|
||||
type: boolean
|
||||
TotalPeers:
|
||||
description: TotalPeers is the total number of peers for this interface.
|
||||
readOnly: true
|
||||
type: integer
|
||||
required:
|
||||
- Identifier
|
||||
- Mode
|
||||
- PrivateKey
|
||||
- PublicKey
|
||||
type: object
|
||||
models.Peer:
|
||||
properties:
|
||||
Addresses:
|
||||
description: Addresses is a list of IP addresses in CIDR format (both IPv4
|
||||
and IPv6) for the peer.
|
||||
example:
|
||||
- 10.11.12.2/24
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
AllowedIPs:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-array_string'
|
||||
description: AllowedIPs is a list of allowed IP subnets for the peer.
|
||||
CheckAliveAddress:
|
||||
description: CheckAliveAddress is an optional ip address or DNS name that
|
||||
is used for ping checks.
|
||||
example: 1.1.1.1
|
||||
type: string
|
||||
Disabled:
|
||||
description: Disabled is a flag that specifies if the peer is enabled or not.
|
||||
Disabled peers are not able to connect.
|
||||
example: false
|
||||
type: boolean
|
||||
DisabledReason:
|
||||
description: DisabledReason is the reason why the peer has been disabled.
|
||||
example: This is a reason why the peer has been disabled.
|
||||
type: string
|
||||
DisplayName:
|
||||
description: DisplayName is a nice display name / description for the peer.
|
||||
example: My Peer
|
||||
maxLength: 64
|
||||
type: string
|
||||
Dns:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-array_string'
|
||||
description: Dns is a list of DNS servers that should be set if the peer interface
|
||||
is up.
|
||||
DnsSearch:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-array_string'
|
||||
description: DnsSearch is the dns search option string that should be set
|
||||
if the peer interface is up, will be appended to Dns servers.
|
||||
Endpoint:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-string'
|
||||
description: Endpoint is the endpoint address of the peer.
|
||||
EndpointPublicKey:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-string'
|
||||
description: EndpointPublicKey is the endpoint public key.
|
||||
ExpiresAt:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ExpiryDate'
|
||||
description: ExpiresAt is the expiry date of the peer in YYYY-MM-DD format.
|
||||
An expired peer is not able to connect.
|
||||
ExtraAllowedIPs:
|
||||
description: ExtraAllowedIPs is a list of additional allowed IP subnets for
|
||||
the peer. These allowed IP subnets are added on the server side.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
FirewallMark:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-uint32'
|
||||
description: FirewallMark is an optional firewall mark which is used to handle
|
||||
peer traffic.
|
||||
Identifier:
|
||||
description: Identifier is the unique identifier of the peer. It is always
|
||||
equal to the public key of the peer.
|
||||
example: xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
|
||||
type: string
|
||||
InterfaceIdentifier:
|
||||
description: InterfaceIdentifier is the identifier of the interface the peer
|
||||
is linked to.
|
||||
example: wg0
|
||||
type: string
|
||||
Mode:
|
||||
description: Mode is the peer interface type (server, client, any).
|
||||
enum:
|
||||
- server
|
||||
- client
|
||||
- any
|
||||
example: client
|
||||
type: string
|
||||
Mtu:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-int'
|
||||
description: Mtu is the device MTU of the peer.
|
||||
Notes:
|
||||
description: Notes is a note field for peers.
|
||||
example: This is a note for the peer.
|
||||
type: string
|
||||
PersistentKeepalive:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-int'
|
||||
description: PersistentKeepalive is the optional persistent keep-alive interval
|
||||
in seconds.
|
||||
PostDown:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-string'
|
||||
description: PostDown is an optional action that is executed after the device
|
||||
is down.
|
||||
PostUp:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-string'
|
||||
description: PostUp is an optional action that is executed after the device
|
||||
is up.
|
||||
PreDown:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-string'
|
||||
description: PreDown is an optional action that is executed before the device
|
||||
is down.
|
||||
PreUp:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-string'
|
||||
description: PreUp is an optional action that is executed before the device
|
||||
is up.
|
||||
PresharedKey:
|
||||
description: PresharedKey is the optional pre-shared Key of the peer.
|
||||
example: yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
|
||||
type: string
|
||||
PrivateKey:
|
||||
description: PrivateKey is the private Key of the peer.
|
||||
example: yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
|
||||
type: string
|
||||
PublicKey:
|
||||
description: PublicKey is the public Key of the server peer.
|
||||
example: TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
|
||||
type: string
|
||||
RoutingTable:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.ConfigOption-string'
|
||||
description: RoutingTable is an optional routing table which is used to route
|
||||
peer traffic.
|
||||
UserIdentifier:
|
||||
description: UserIdentifier is the identifier of the user that owns the peer.
|
||||
example: uid-1234567
|
||||
type: string
|
||||
required:
|
||||
- Identifier
|
||||
- InterfaceIdentifier
|
||||
- PrivateKey
|
||||
type: object
|
||||
models.User:
|
||||
properties:
|
||||
ApiEnabled:
|
||||
description: If this field is set, the user is allowed to use the RESTful
|
||||
API. This field is read-only.
|
||||
example: false
|
||||
readOnly: true
|
||||
type: boolean
|
||||
ApiToken:
|
||||
description: The API token of the user. This field is never populated on bulk
|
||||
read operations.
|
||||
example: ""
|
||||
maxLength: 64
|
||||
minLength: 32
|
||||
type: string
|
||||
Department:
|
||||
description: The department of the user. This field is optional.
|
||||
example: Software Development
|
||||
type: string
|
||||
Disabled:
|
||||
description: If this field is set, the user is disabled.
|
||||
example: false
|
||||
type: boolean
|
||||
DisabledReason:
|
||||
description: The reason why the user has been disabled.
|
||||
example: ""
|
||||
type: string
|
||||
Email:
|
||||
description: The email address of the user. This field is optional.
|
||||
example: test@test.com
|
||||
type: string
|
||||
Firstname:
|
||||
description: The first name of the user. This field is optional.
|
||||
example: Max
|
||||
type: string
|
||||
Identifier:
|
||||
description: The unique identifier of the user.
|
||||
example: uid-1234567
|
||||
maxLength: 64
|
||||
type: string
|
||||
IsAdmin:
|
||||
description: If this field is set, the user is an admin.
|
||||
example: false
|
||||
type: boolean
|
||||
Lastname:
|
||||
description: The last name of the user. This field is optional.
|
||||
example: Muster
|
||||
type: string
|
||||
Locked:
|
||||
description: If this field is set, the user is locked and thus unable to log
|
||||
in to WireGuard Portal.
|
||||
example: false
|
||||
type: boolean
|
||||
LockedReason:
|
||||
description: The reason why the user has been locked.
|
||||
example: ""
|
||||
type: string
|
||||
Notes:
|
||||
description: Additional notes about the user. This field is optional.
|
||||
example: some sample notes
|
||||
type: string
|
||||
Password:
|
||||
description: The password of the user. This field is never populated on read
|
||||
operations.
|
||||
example: ""
|
||||
maxLength: 64
|
||||
minLength: 16
|
||||
type: string
|
||||
PeerCount:
|
||||
description: The number of peers linked to the user. This field is read-only.
|
||||
example: 2
|
||||
readOnly: true
|
||||
type: integer
|
||||
Phone:
|
||||
description: The phone number of the user. This field is optional.
|
||||
example: "+1234546789"
|
||||
type: string
|
||||
ProviderName:
|
||||
description: The name of the authentication provider. This field is optional.
|
||||
description: The name of the authentication provider. This field is read-only.
|
||||
example: ""
|
||||
readOnly: true
|
||||
type: string
|
||||
Source:
|
||||
description: The source of the user. This field is optional.
|
||||
enum:
|
||||
- db
|
||||
example: db
|
||||
type: string
|
||||
required:
|
||||
- Identifier
|
||||
- IsAdmin
|
||||
type: object
|
||||
info:
|
||||
contact:
|
||||
name: WireGuard Portal Project
|
||||
url: https://github.com/h44z/wg-portal
|
||||
description: WireGuard Portal API for managing users and peers.
|
||||
description: |-
|
||||
The WireGuard Portal REST API enables efficient management of WireGuard VPN configurations through a set of JSON-based endpoints.
|
||||
It supports creating and editing peers, interfaces, and user profiles, while also providing role-based access control and auditing.
|
||||
This API allows seamless integration with external tools or scripts for automated network configuration and administration.
|
||||
license:
|
||||
name: MIT
|
||||
url: https://github.com/h44z/wg-portal/blob/master/LICENSE.txt
|
||||
title: WireGuard Portal Public API
|
||||
version: "1.0"
|
||||
paths:
|
||||
/interface/all:
|
||||
get:
|
||||
operationId: interface_handleAllGet
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/models.Interface'
|
||||
type: array
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/models.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.Error'
|
||||
security:
|
||||
- BasicAuth: []
|
||||
summary: Get all interface records.
|
||||
tags:
|
||||
- Interfaces
|
||||
/interface/by-id/{id}:
|
||||
delete:
|
||||
operationId: interfaces_handleDelete
|
||||
parameters:
|
||||
- description: The interface 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 interface record.
|
||||
tags:
|
||||
- Interfaces
|
||||
get:
|
||||
operationId: interfaces_handleByIdGet
|
||||
parameters:
|
||||
- description: The interface identifier.
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Interface'
|
||||
"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 a specific interface record by its identifier.
|
||||
tags:
|
||||
- Interfaces
|
||||
put:
|
||||
operationId: interfaces_handleUpdatePut
|
||||
parameters:
|
||||
- description: The interface identifier.
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: The interface data.
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.Interface'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Interface'
|
||||
"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 an interface record.
|
||||
tags:
|
||||
- Interfaces
|
||||
/interface/new:
|
||||
post:
|
||||
operationId: interfaces_handleCreatePost
|
||||
parameters:
|
||||
- description: The interface data.
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.Interface'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Interface'
|
||||
"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 interface record.
|
||||
tags:
|
||||
- Interfaces
|
||||
/peer/by-id/{id}:
|
||||
delete:
|
||||
operationId: peers_handleDelete
|
||||
parameters:
|
||||
- description: The peer 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 peer record.
|
||||
tags:
|
||||
- Peers
|
||||
get:
|
||||
description: Normal users can only access their own records. Admins can access
|
||||
all records.
|
||||
operationId: peers_handleByIdGet
|
||||
parameters:
|
||||
- description: The peer identifier (public key).
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Peer'
|
||||
"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 a specific peer record by its identifier (public key).
|
||||
tags:
|
||||
- Peers
|
||||
put:
|
||||
description: Only admins can update existing records.
|
||||
operationId: peers_handleUpdatePut
|
||||
parameters:
|
||||
- description: The peer identifier.
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: The peer data.
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.Peer'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Peer'
|
||||
"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 peer record.
|
||||
tags:
|
||||
- Peers
|
||||
/peer/by-interface/{id}:
|
||||
get:
|
||||
operationId: peers_handleAllForInterfaceGet
|
||||
parameters:
|
||||
- description: The WireGuard interface identifier.
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/models.Peer'
|
||||
type: array
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/models.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.Error'
|
||||
security:
|
||||
- BasicAuth: []
|
||||
summary: Get all peer records for a given WireGuard interface.
|
||||
tags:
|
||||
- Peers
|
||||
/peer/by-user/{id}:
|
||||
get:
|
||||
description: Normal users can only access their own records. Admins can access
|
||||
all records.
|
||||
operationId: peers_handleAllForUserGet
|
||||
parameters:
|
||||
- description: The user identifier.
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/models.Peer'
|
||||
type: array
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/models.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/models.Error'
|
||||
security:
|
||||
- BasicAuth: []
|
||||
summary: Get all peer records for a given user.
|
||||
tags:
|
||||
- Peers
|
||||
/peer/new:
|
||||
post:
|
||||
description: Only admins can create new records.
|
||||
operationId: peers_handleCreatePost
|
||||
parameters:
|
||||
- description: The peer data.
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.Peer'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Peer'
|
||||
"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 peer record.
|
||||
tags:
|
||||
- Peers
|
||||
/user/all:
|
||||
get:
|
||||
operationId: users_handleAllGet
|
||||
@ -109,11 +933,11 @@ paths:
|
||||
summary: Get all user records.
|
||||
tags:
|
||||
- Users
|
||||
/user/id/{id}:
|
||||
/user/by-id/{id}:
|
||||
delete:
|
||||
operationId: users_handleDelete
|
||||
parameters:
|
||||
- description: The user identifier
|
||||
- description: The user identifier.
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
@ -122,7 +946,7 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No content if deletion was successful
|
||||
description: No content if deletion was successful.
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
@ -190,12 +1014,12 @@ paths:
|
||||
description: Only admins can update existing records.
|
||||
operationId: users_handleUpdatePut
|
||||
parameters:
|
||||
- description: The user identifier
|
||||
- description: The user identifier.
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: The user data
|
||||
- description: The user data.
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
|
3917
internal/app/api/core/assets/js/rapidoc-min.js
vendored
3917
internal/app/api/core/assets/js/rapidoc-min.js
vendored
File diff suppressed because one or more lines are too long
@ -8,10 +8,16 @@
|
||||
<rapi-doc
|
||||
spec-url="{{ $.ApiSpecUrl }}"
|
||||
theme="dark"
|
||||
render-style="focused"
|
||||
allow-server-selection="false"
|
||||
allow-authentication="true"
|
||||
load-fonts="false"
|
||||
schema-style="table"
|
||||
schema-expand-level="1"
|
||||
default-schema-tab="model"
|
||||
fill-request-fields-with-example="true"
|
||||
show-method-in-nav-bar="as-colored-block"
|
||||
show-components="true"
|
||||
allow-spec-url-load="false"
|
||||
allow-spec-file-load="false"
|
||||
allow-spec-file-download="true"
|
||||
|
@ -88,6 +88,8 @@ func NewServer(cfg *config.Config, endpoints ...ApiEndpointSetupFunc) (*Server,
|
||||
s.server.StaticFS("/doc", http.FS(fsMust(fs.Sub(apiStatics, "assets/doc"))))
|
||||
|
||||
// Setup routes
|
||||
s.server.UseRawPath = true
|
||||
s.server.UnescapePathValues = true
|
||||
s.setupRoutes(endpoints...)
|
||||
s.setupFrontendRoutes()
|
||||
|
||||
|
@ -4,13 +4,14 @@ import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/h44z/wg-portal/internal/app"
|
||||
"github.com/h44z/wg-portal/internal/app/api/v0/model"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/h44z/wg-portal/internal/app"
|
||||
"github.com/h44z/wg-portal/internal/app/api/v0/model"
|
||||
)
|
||||
|
||||
//go:embed frontend_config.js.gotpl
|
||||
@ -63,7 +64,8 @@ func (e configEndpoint) handleConfigJsGet() gin.HandlerFunc {
|
||||
if err == nil {
|
||||
host, port, _ = net.SplitHostPort(parsedReferer.Host)
|
||||
}
|
||||
backendUrl = fmt.Sprintf("http://%s:%s/api/v0", host, port) // override if request comes from frontend started with npm run dev
|
||||
backendUrl = fmt.Sprintf("http://%s:%s/api/v0", host,
|
||||
port) // override if request comes from frontend started with npm run dev
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
err := e.tpl.ExecuteTemplate(buf, "frontend_config.js.gotpl", gin.H{
|
||||
@ -96,6 +98,7 @@ func (e configEndpoint) handleSettingsGet() gin.HandlerFunc {
|
||||
MailLinkOnly: e.app.Config.Mail.LinkOnly,
|
||||
PersistentConfigSupported: e.app.Config.Advanced.ConfigStoragePath != "",
|
||||
SelfProvisioning: e.app.Config.Core.SelfProvisioningAllowed,
|
||||
ApiAdminOnly: e.app.Config.Advanced.ApiAdminOnly,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -302,7 +302,7 @@ func (e userEndpoint) handleApiEnablePost() gin.HandlerFunc {
|
||||
// @Success 200 {object} model.User
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /user/{id}/api/enable [post]
|
||||
// @Router /user/{id}/api/disable [post]
|
||||
func (e userEndpoint) handleApiDisablePost() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
@ -9,4 +9,5 @@ type Settings struct {
|
||||
MailLinkOnly bool `json:"MailLinkOnly"`
|
||||
PersistentConfigSupported bool `json:"PersistentConfigSupported"`
|
||||
SelfProvisioning bool `json:"SelfProvisioning"`
|
||||
ApiAdminOnly bool `json:"ApiAdminOnly"`
|
||||
}
|
||||
|
109
internal/app/api/v1/backend/interface_service.go
Normal file
109
internal/app/api/v1/backend/interface_service.go
Normal file
@ -0,0 +1,109 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
type InterfaceServiceInterfaceManagerRepo interface {
|
||||
GetAllInterfacesAndPeers(ctx context.Context) ([]domain.Interface, [][]domain.Peer, error)
|
||||
GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error)
|
||||
CreateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error)
|
||||
UpdateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, []domain.Peer, error)
|
||||
DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error
|
||||
}
|
||||
|
||||
type InterfaceService struct {
|
||||
cfg *config.Config
|
||||
|
||||
interfaces InterfaceServiceInterfaceManagerRepo
|
||||
users PeerServiceUserManagerRepo
|
||||
}
|
||||
|
||||
func NewInterfaceService(cfg *config.Config, interfaces InterfaceServiceInterfaceManagerRepo) *InterfaceService {
|
||||
return &InterfaceService{
|
||||
cfg: cfg,
|
||||
interfaces: interfaces,
|
||||
}
|
||||
}
|
||||
|
||||
func (s InterfaceService) GetAll(ctx context.Context) ([]domain.Interface, [][]domain.Peer, error) {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
interfaces, interfacePeers, err := s.interfaces.GetAllInterfacesAndPeers(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return interfaces, interfacePeers, nil
|
||||
}
|
||||
|
||||
func (s InterfaceService) GetById(ctx context.Context, id domain.InterfaceIdentifier) (
|
||||
*domain.Interface,
|
||||
[]domain.Peer,
|
||||
error,
|
||||
) {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
interfaceData, interfacePeers, err := s.interfaces.GetInterfaceAndPeers(ctx, id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return interfaceData, interfacePeers, nil
|
||||
}
|
||||
|
||||
func (s InterfaceService) Create(ctx context.Context, iface *domain.Interface) (*domain.Interface, error) {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createdInterface, err := s.interfaces.CreateInterface(ctx, iface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createdInterface, nil
|
||||
}
|
||||
|
||||
func (s InterfaceService) Update(ctx context.Context, id domain.InterfaceIdentifier, iface *domain.Interface) (
|
||||
*domain.Interface,
|
||||
[]domain.Peer,
|
||||
error,
|
||||
) {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if iface.Identifier != id {
|
||||
return nil, nil, fmt.Errorf("interface id mismatch: %s != %s: %w",
|
||||
iface.Identifier, id, domain.ErrInvalidData)
|
||||
}
|
||||
|
||||
updatedInterface, updatedPeers, err := s.interfaces.UpdateInterface(ctx, iface)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return updatedInterface, updatedPeers, nil
|
||||
}
|
||||
|
||||
func (s InterfaceService) Delete(ctx context.Context, id domain.InterfaceIdentifier) error {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := s.interfaces.DeleteInterface(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
143
internal/app/api/v1/backend/peer_service.go
Normal file
143
internal/app/api/v1/backend/peer_service.go
Normal file
@ -0,0 +1,143 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
type PeerServicePeerManagerRepo interface {
|
||||
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
|
||||
GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error)
|
||||
GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error)
|
||||
CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error)
|
||||
UpdatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error)
|
||||
DeletePeer(ctx context.Context, id domain.PeerIdentifier) error
|
||||
}
|
||||
|
||||
type PeerServiceUserManagerRepo interface {
|
||||
GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
|
||||
}
|
||||
|
||||
type PeerService struct {
|
||||
cfg *config.Config
|
||||
|
||||
peers PeerServicePeerManagerRepo
|
||||
users PeerServiceUserManagerRepo
|
||||
}
|
||||
|
||||
func NewPeerService(
|
||||
cfg *config.Config,
|
||||
peers PeerServicePeerManagerRepo,
|
||||
users PeerServiceUserManagerRepo,
|
||||
) *PeerService {
|
||||
return &PeerService{
|
||||
cfg: cfg,
|
||||
peers: peers,
|
||||
users: users,
|
||||
}
|
||||
}
|
||||
|
||||
func (s PeerService) GetForInterface(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.Peer, error) {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, interfacePeers, err := s.peers.GetInterfaceAndPeers(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return interfacePeers, nil
|
||||
}
|
||||
|
||||
func (s PeerService) GetForUser(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error) {
|
||||
if err := domain.ValidateUserAccessRights(ctx, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.cfg.Advanced.ApiAdminOnly && !domain.GetUserInfo(ctx).IsAdmin {
|
||||
return nil, errors.Join(errors.New("only admins can access this endpoint"), domain.ErrNoPermission)
|
||||
}
|
||||
|
||||
user, err := s.users.GetUser(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userPeers, err := s.peers.GetUserPeers(ctx, user.Identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return userPeers, nil
|
||||
}
|
||||
|
||||
func (s PeerService) GetById(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) {
|
||||
if s.cfg.Advanced.ApiAdminOnly && !domain.GetUserInfo(ctx).IsAdmin {
|
||||
return nil, errors.Join(errors.New("only admins can access this endpoint"), domain.ErrNoPermission)
|
||||
}
|
||||
|
||||
peer, err := s.peers.GetPeer(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if the user has access rights to the requested peer.
|
||||
// If the peer is not linked to any user, access is granted only for admins.
|
||||
if err := domain.ValidateUserAccessRights(ctx, peer.UserIdentifier); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
func (s PeerService) Create(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if peer.Identifier != domain.PeerIdentifier(peer.Interface.PublicKey) {
|
||||
return nil, fmt.Errorf("peer id mismatch: %s != %s: %w",
|
||||
peer.Identifier, peer.Interface.PublicKey, domain.ErrInvalidData)
|
||||
}
|
||||
|
||||
createdPeer, err := s.peers.CreatePeer(ctx, peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createdPeer, nil
|
||||
}
|
||||
|
||||
func (s PeerService) Update(ctx context.Context, _ domain.PeerIdentifier, peer *domain.Peer) (
|
||||
*domain.Peer,
|
||||
error,
|
||||
) {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedPeer, err := s.peers.UpdatePeer(ctx, peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return updatedPeer, nil
|
||||
}
|
||||
|
||||
func (s PeerService) Delete(ctx context.Context, id domain.PeerIdentifier) error {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := s.peers.DeletePeer(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -30,7 +30,7 @@ func NewUserService(cfg *config.Config, users UserManagerRepo) *UserService {
|
||||
}
|
||||
}
|
||||
|
||||
func (s UserService) GetUsers(ctx context.Context) ([]domain.User, error) {
|
||||
func (s UserService) GetAll(ctx context.Context) ([]domain.User, error) {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -43,9 +43,9 @@ func (s UserService) GetUsers(ctx context.Context) ([]domain.User, error) {
|
||||
return allUsers, nil
|
||||
}
|
||||
|
||||
func (s UserService) GetUserById(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) {
|
||||
func (s UserService) GetById(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) {
|
||||
if err := domain.ValidateUserAccessRights(ctx, id); err != nil {
|
||||
return nil, errors.Join(err, domain.ErrNoPermission)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.cfg.Advanced.ApiAdminOnly && !domain.GetUserInfo(ctx).IsAdmin {
|
||||
@ -60,7 +60,7 @@ func (s UserService) GetUserById(ctx context.Context, id domain.UserIdentifier)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s UserService) CreateUser(ctx context.Context, user *domain.User) (*domain.User, error) {
|
||||
func (s UserService) Create(ctx context.Context, user *domain.User) (*domain.User, error) {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -73,7 +73,7 @@ func (s UserService) CreateUser(ctx context.Context, user *domain.User) (*domain
|
||||
return createdUser, nil
|
||||
}
|
||||
|
||||
func (s UserService) UpdateUser(ctx context.Context, id domain.UserIdentifier, user *domain.User) (
|
||||
func (s UserService) Update(ctx context.Context, id domain.UserIdentifier, user *domain.User) (
|
||||
*domain.User,
|
||||
error,
|
||||
) {
|
||||
@ -93,7 +93,7 @@ func (s UserService) UpdateUser(ctx context.Context, id domain.UserIdentifier, u
|
||||
return updatedUser, nil
|
||||
}
|
||||
|
||||
func (s UserService) DeleteUser(ctx context.Context, id domain.UserIdentifier) error {
|
||||
func (s UserService) Delete(ctx context.Context, id domain.UserIdentifier) error {
|
||||
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -22,7 +22,9 @@ type Handler interface {
|
||||
|
||||
// @title WireGuard Portal Public API
|
||||
// @version 1.0
|
||||
// @description WireGuard Portal API for managing users and peers.
|
||||
// @description The WireGuard Portal REST API enables efficient management of WireGuard VPN configurations through a set of JSON-based endpoints.
|
||||
// @description It supports creating and editing peers, interfaces, and user profiles, while also providing role-based access control and auditing.
|
||||
// @description This API allows seamless integration with external tools or scripts for automated network configuration and administration.
|
||||
|
||||
// @license.name MIT
|
||||
// @license.url https://github.com/h44z/wg-portal/blob/master/LICENSE.txt
|
||||
|
220
internal/app/api/v1/handlers/endpoint_interface.go
Normal file
220
internal/app/api/v1/handlers/endpoint_interface.go
Normal file
@ -0,0 +1,220 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/h44z/wg-portal/internal/app/api/v1/models"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
type InterfaceEndpointInterfaceService interface {
|
||||
GetAll(context.Context) ([]domain.Interface, [][]domain.Peer, error)
|
||||
GetById(context.Context, domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error)
|
||||
Create(context.Context, *domain.Interface) (*domain.Interface, error)
|
||||
Update(context.Context, domain.InterfaceIdentifier, *domain.Interface) (*domain.Interface, []domain.Peer, error)
|
||||
Delete(context.Context, domain.InterfaceIdentifier) error
|
||||
}
|
||||
|
||||
type InterfaceEndpoint struct {
|
||||
interfaces InterfaceEndpointInterfaceService
|
||||
}
|
||||
|
||||
func NewInterfaceEndpoint(interfaceService InterfaceEndpointInterfaceService) *InterfaceEndpoint {
|
||||
return &InterfaceEndpoint{
|
||||
interfaces: interfaceService,
|
||||
}
|
||||
}
|
||||
|
||||
func (e InterfaceEndpoint) GetName() string {
|
||||
return "InterfaceEndpoint"
|
||||
}
|
||||
|
||||
func (e InterfaceEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
|
||||
apiGroup := g.Group("/interface", authenticator.LoggedIn())
|
||||
|
||||
apiGroup.GET("/all", authenticator.LoggedIn(ScopeAdmin), e.handleAllGet())
|
||||
apiGroup.GET("/by-id/:id", authenticator.LoggedIn(ScopeAdmin), e.handleByIdGet())
|
||||
|
||||
apiGroup.POST("/new", authenticator.LoggedIn(ScopeAdmin), e.handleCreatePost())
|
||||
apiGroup.PUT("/by-id/:id", authenticator.LoggedIn(ScopeAdmin), e.handleUpdatePut())
|
||||
apiGroup.DELETE("/by-id/:id", authenticator.LoggedIn(ScopeAdmin), e.handleDelete())
|
||||
}
|
||||
|
||||
// handleAllGet returns a gorm Handler function.
|
||||
//
|
||||
// @ID interface_handleAllGet
|
||||
// @Tags Interfaces
|
||||
// @Summary Get all interface records.
|
||||
// @Produce json
|
||||
// @Success 200 {object} []models.Interface
|
||||
// @Failure 401 {object} models.Error
|
||||
// @Failure 500 {object} models.Error
|
||||
// @Router /interface/all [get]
|
||||
// @Security BasicAuth
|
||||
func (e InterfaceEndpoint) handleAllGet() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
allInterfaces, allPeersPerInterface, err := e.interfaces.GetAll(ctx)
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.NewInterfaces(allInterfaces, allPeersPerInterface))
|
||||
}
|
||||
}
|
||||
|
||||
// handleByIdGet returns a gorm Handler function.
|
||||
//
|
||||
// @ID interfaces_handleByIdGet
|
||||
// @Tags Interfaces
|
||||
// @Summary Get a specific interface record by its identifier.
|
||||
// @Param id path string true "The interface identifier."
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.Interface
|
||||
// @Failure 401 {object} models.Error
|
||||
// @Failure 403 {object} models.Error
|
||||
// @Failure 404 {object} models.Error
|
||||
// @Failure 500 {object} models.Error
|
||||
// @Router /interface/by-id/{id} [get]
|
||||
// @Security BasicAuth
|
||||
func (e InterfaceEndpoint) handleByIdGet() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing interface id"})
|
||||
return
|
||||
}
|
||||
|
||||
iface, interfacePeers, err := e.interfaces.GetById(ctx, domain.InterfaceIdentifier(id))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.NewInterface(iface, interfacePeers))
|
||||
}
|
||||
}
|
||||
|
||||
// handleCreatePost returns a gorm handler function.
|
||||
//
|
||||
// @ID interfaces_handleCreatePost
|
||||
// @Tags Interfaces
|
||||
// @Summary Create a new interface record.
|
||||
// @Param request body models.Interface true "The interface data."
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.Interface
|
||||
// @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 /interface/new [post]
|
||||
// @Security BasicAuth
|
||||
func (e InterfaceEndpoint) handleCreatePost() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
var iface models.Interface
|
||||
err := c.BindJSON(&iface)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
newInterface, err := e.interfaces.Create(ctx, models.NewDomainInterface(&iface))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.NewInterface(newInterface, nil))
|
||||
}
|
||||
}
|
||||
|
||||
// handleUpdatePut returns a gorm handler function.
|
||||
//
|
||||
// @ID interfaces_handleUpdatePut
|
||||
// @Tags Interfaces
|
||||
// @Summary Update an interface record.
|
||||
// @Param id path string true "The interface identifier."
|
||||
// @Param request body models.Interface true "The interface data."
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.Interface
|
||||
// @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 /interface/by-id/{id} [put]
|
||||
// @Security BasicAuth
|
||||
func (e InterfaceEndpoint) handleUpdatePut() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing interface id"})
|
||||
return
|
||||
}
|
||||
|
||||
var iface models.Interface
|
||||
err := c.BindJSON(&iface)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
updatedInterface, updatedInterfacePeers, err := e.interfaces.Update(
|
||||
ctx,
|
||||
domain.InterfaceIdentifier(id),
|
||||
models.NewDomainInterface(&iface),
|
||||
)
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.NewInterface(updatedInterface, updatedInterfacePeers))
|
||||
}
|
||||
}
|
||||
|
||||
// handleDelete returns a gorm handler function.
|
||||
//
|
||||
// @ID interfaces_handleDelete
|
||||
// @Tags Interfaces
|
||||
// @Summary Delete the interface record.
|
||||
// @Param id path string true "The interface identifier."
|
||||
// @Produce json
|
||||
// @Success 204 "No content if deletion was successful."
|
||||
// @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 /interface/by-id/{id} [delete]
|
||||
// @Security BasicAuth
|
||||
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, models.Error{Code: http.StatusBadRequest, Message: "missing interface id"})
|
||||
return
|
||||
}
|
||||
|
||||
err := e.interfaces.Delete(ctx, domain.InterfaceIdentifier(id))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
261
internal/app/api/v1/handlers/endpoint_peer.go
Normal file
261
internal/app/api/v1/handlers/endpoint_peer.go
Normal file
@ -0,0 +1,261 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/h44z/wg-portal/internal/app/api/v1/models"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
type PeerService interface {
|
||||
GetForInterface(context.Context, domain.InterfaceIdentifier) ([]domain.Peer, error)
|
||||
GetForUser(context.Context, domain.UserIdentifier) ([]domain.Peer, error)
|
||||
GetById(context.Context, domain.PeerIdentifier) (*domain.Peer, error)
|
||||
Create(context.Context, *domain.Peer) (*domain.Peer, error)
|
||||
Update(context.Context, domain.PeerIdentifier, *domain.Peer) (*domain.Peer, error)
|
||||
Delete(context.Context, domain.PeerIdentifier) error
|
||||
}
|
||||
|
||||
type PeerEndpoint struct {
|
||||
peers PeerService
|
||||
}
|
||||
|
||||
func NewPeerEndpoint(peerService PeerService) *PeerEndpoint {
|
||||
return &PeerEndpoint{
|
||||
peers: peerService,
|
||||
}
|
||||
}
|
||||
|
||||
func (e PeerEndpoint) GetName() string {
|
||||
return "PeerEndpoint"
|
||||
}
|
||||
|
||||
func (e PeerEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenticationHandler) {
|
||||
apiGroup := g.Group("/peer", authenticator.LoggedIn())
|
||||
|
||||
apiGroup.GET("/by-interface/:id", authenticator.LoggedIn(ScopeAdmin), e.handleAllForInterfaceGet())
|
||||
apiGroup.GET("/by-user/:id", authenticator.LoggedIn(), e.handleAllForUserGet())
|
||||
apiGroup.GET("/by-id/:id", authenticator.LoggedIn(), e.handleByIdGet())
|
||||
|
||||
apiGroup.POST("/new", authenticator.LoggedIn(ScopeAdmin), e.handleCreatePost())
|
||||
apiGroup.PUT("/by-id/:id", authenticator.LoggedIn(ScopeAdmin), e.handleUpdatePut())
|
||||
apiGroup.DELETE("/by-id/:id", authenticator.LoggedIn(ScopeAdmin), e.handleDelete())
|
||||
}
|
||||
|
||||
// handleAllForInterfaceGet returns a gorm Handler function.
|
||||
//
|
||||
// @ID peers_handleAllForInterfaceGet
|
||||
// @Tags Peers
|
||||
// @Summary Get all peer records for a given WireGuard interface.
|
||||
// @Param id path string true "The WireGuard interface identifier."
|
||||
// @Produce json
|
||||
// @Success 200 {object} []models.Peer
|
||||
// @Failure 401 {object} models.Error
|
||||
// @Failure 500 {object} models.Error
|
||||
// @Router /peer/by-interface/{id} [get]
|
||||
// @Security BasicAuth
|
||||
func (e PeerEndpoint) handleAllForInterfaceGet() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing interface id"})
|
||||
return
|
||||
}
|
||||
|
||||
interfacePeers, err := e.peers.GetForInterface(ctx, domain.InterfaceIdentifier(id))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.NewPeers(interfacePeers))
|
||||
}
|
||||
}
|
||||
|
||||
// handleAllForUserGet returns a gorm Handler function.
|
||||
//
|
||||
// @ID peers_handleAllForUserGet
|
||||
// @Tags Peers
|
||||
// @Summary Get all peer records for a given user.
|
||||
// @Description Normal users can only access their own records. Admins can access all records.
|
||||
// @Param id path string true "The user identifier."
|
||||
// @Produce json
|
||||
// @Success 200 {object} []models.Peer
|
||||
// @Failure 401 {object} models.Error
|
||||
// @Failure 500 {object} models.Error
|
||||
// @Router /peer/by-user/{id} [get]
|
||||
// @Security BasicAuth
|
||||
func (e PeerEndpoint) handleAllForUserGet() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing user id"})
|
||||
return
|
||||
}
|
||||
|
||||
interfacePeers, err := e.peers.GetForUser(ctx, domain.UserIdentifier(id))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.NewPeers(interfacePeers))
|
||||
}
|
||||
}
|
||||
|
||||
// handleByIdGet returns a gorm Handler function.
|
||||
//
|
||||
// @ID peers_handleByIdGet
|
||||
// @Tags Peers
|
||||
// @Summary Get a specific peer record by its identifier (public key).
|
||||
// @Description Normal users can only access their own records. Admins can access all records.
|
||||
// @Param id path string true "The peer identifier (public key)."
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.Peer
|
||||
// @Failure 401 {object} models.Error
|
||||
// @Failure 403 {object} models.Error
|
||||
// @Failure 404 {object} models.Error
|
||||
// @Failure 500 {object} models.Error
|
||||
// @Router /peer/by-id/{id} [get]
|
||||
// @Security BasicAuth
|
||||
func (e PeerEndpoint) handleByIdGet() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing peer id"})
|
||||
return
|
||||
}
|
||||
|
||||
peer, err := e.peers.GetById(ctx, domain.PeerIdentifier(id))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.NewPeer(peer))
|
||||
}
|
||||
}
|
||||
|
||||
// handleCreatePost returns a gorm handler function.
|
||||
//
|
||||
// @ID peers_handleCreatePost
|
||||
// @Tags Peers
|
||||
// @Summary Create a new peer record.
|
||||
// @Description Only admins can create new records.
|
||||
// @Param request body models.Peer true "The peer data."
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.Peer
|
||||
// @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 /peer/new [post]
|
||||
// @Security BasicAuth
|
||||
func (e PeerEndpoint) handleCreatePost() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
var peer models.Peer
|
||||
err := c.BindJSON(&peer)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
newPeer, err := e.peers.Create(ctx, models.NewDomainPeer(&peer))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.NewPeer(newPeer))
|
||||
}
|
||||
}
|
||||
|
||||
// handleUpdatePut returns a gorm handler function.
|
||||
//
|
||||
// @ID peers_handleUpdatePut
|
||||
// @Tags Peers
|
||||
// @Summary Update a peer record.
|
||||
// @Description Only admins can update existing records.
|
||||
// @Param id path string true "The peer identifier."
|
||||
// @Param request body models.Peer true "The peer data."
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.Peer
|
||||
// @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 /peer/by-id/{id} [put]
|
||||
// @Security BasicAuth
|
||||
func (e PeerEndpoint) handleUpdatePut() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing peer id"})
|
||||
return
|
||||
}
|
||||
|
||||
var peer models.Peer
|
||||
err := c.BindJSON(&peer)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
updatedPeer, err := e.peers.Update(ctx, domain.PeerIdentifier(id), models.NewDomainPeer(&peer))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.NewPeer(updatedPeer))
|
||||
}
|
||||
}
|
||||
|
||||
// handleDelete returns a gorm handler function.
|
||||
//
|
||||
// @ID peers_handleDelete
|
||||
// @Tags Peers
|
||||
// @Summary Delete the peer record.
|
||||
// @Param id path string true "The peer identifier."
|
||||
// @Produce json
|
||||
// @Success 204 "No content if deletion was successful."
|
||||
// @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 /peer/by-id/{id} [delete]
|
||||
// @Security BasicAuth
|
||||
func (e PeerEndpoint) handleDelete() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
id := c.Param("id")
|
||||
if id == "" {
|
||||
c.JSON(http.StatusBadRequest, models.Error{Code: http.StatusBadRequest, Message: "missing peer id"})
|
||||
return
|
||||
}
|
||||
|
||||
err := e.peers.Delete(ctx, domain.PeerIdentifier(id))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
@ -10,11 +10,11 @@ import (
|
||||
)
|
||||
|
||||
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
|
||||
GetAll(ctx context.Context) ([]domain.User, error)
|
||||
GetById(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
|
||||
Create(ctx context.Context, user *domain.User) (*domain.User, error)
|
||||
Update(ctx context.Context, id domain.UserIdentifier, user *domain.User) (*domain.User, error)
|
||||
Delete(ctx context.Context, id domain.UserIdentifier) error
|
||||
}
|
||||
|
||||
type UserEndpoint struct {
|
||||
@ -35,10 +35,10 @@ func (e UserEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti
|
||||
apiGroup := g.Group("/user", authenticator.LoggedIn())
|
||||
|
||||
apiGroup.GET("/all", authenticator.LoggedIn(ScopeAdmin), e.handleAllGet())
|
||||
apiGroup.GET("/id/:id", authenticator.LoggedIn(), e.handleByIdGet())
|
||||
apiGroup.GET("/by-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())
|
||||
apiGroup.PUT("/by-id/:id", authenticator.LoggedIn(ScopeAdmin), e.handleUpdatePut())
|
||||
apiGroup.DELETE("/by-id/:id", authenticator.LoggedIn(ScopeAdmin), e.handleDelete())
|
||||
}
|
||||
|
||||
// handleAllGet returns a gorm Handler function.
|
||||
@ -56,7 +56,7 @@ func (e UserEndpoint) handleAllGet() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
users, err := e.users.GetUsers(ctx)
|
||||
users, err := e.users.GetAll(ctx)
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
@ -79,7 +79,7 @@ func (e UserEndpoint) handleAllGet() gin.HandlerFunc {
|
||||
// @Failure 403 {object} models.Error
|
||||
// @Failure 404 {object} models.Error
|
||||
// @Failure 500 {object} models.Error
|
||||
// @Router /user/id/{id} [get]
|
||||
// @Router /user/by-id/{id} [get]
|
||||
// @Security BasicAuth
|
||||
func (e UserEndpoint) handleByIdGet() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
@ -91,7 +91,7 @@ func (e UserEndpoint) handleByIdGet() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := e.users.GetUserById(ctx, domain.UserIdentifier(id))
|
||||
user, err := e.users.GetById(ctx, domain.UserIdentifier(id))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
@ -128,7 +128,7 @@ func (e UserEndpoint) handleCreatePost() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
newUser, err := e.users.CreateUser(ctx, models.NewDomainUser(&user))
|
||||
newUser, err := e.users.Create(ctx, models.NewDomainUser(&user))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
@ -144,8 +144,8 @@ func (e UserEndpoint) handleCreatePost() gin.HandlerFunc {
|
||||
// @Tags Users
|
||||
// @Summary Update a user record.
|
||||
// @Description Only admins can update existing records.
|
||||
// @Param id path string true "The user identifier"
|
||||
// @Param request body models.User true "The user data"
|
||||
// @Param id path string true "The user identifier."
|
||||
// @Param request body models.User true "The user data."
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.User
|
||||
// @Failure 400 {object} models.Error
|
||||
@ -153,7 +153,7 @@ func (e UserEndpoint) handleCreatePost() gin.HandlerFunc {
|
||||
// @Failure 403 {object} models.Error
|
||||
// @Failure 404 {object} models.Error
|
||||
// @Failure 500 {object} models.Error
|
||||
// @Router /user/id/{id} [put]
|
||||
// @Router /user/by-id/{id} [put]
|
||||
// @Security BasicAuth
|
||||
func (e UserEndpoint) handleUpdatePut() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
@ -172,7 +172,7 @@ func (e UserEndpoint) handleUpdatePut() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
updateUser, err := e.users.UpdateUser(ctx, domain.UserIdentifier(id), models.NewDomainUser(&user))
|
||||
updateUser, err := e.users.Update(ctx, domain.UserIdentifier(id), models.NewDomainUser(&user))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
||||
@ -187,19 +187,18 @@ func (e UserEndpoint) handleUpdatePut() gin.HandlerFunc {
|
||||
// @ID users_handleDelete
|
||||
// @Tags Users
|
||||
// @Summary Delete the user record.
|
||||
// @Param id path string true "The user identifier."
|
||||
// @Produce json
|
||||
// @Param id path string true "The user identifier"
|
||||
// @Success 204 "No content if deletion was successful"
|
||||
// @Success 204 "No content if deletion was successful."
|
||||
// @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]
|
||||
// @Router /user/by-id/{id} [delete]
|
||||
// @Security BasicAuth
|
||||
func (e UserEndpoint) handleDelete() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// TODO: implement
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
id := c.Param("id")
|
||||
@ -208,7 +207,7 @@ func (e UserEndpoint) handleDelete() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
err := e.users.DeleteUser(ctx, domain.UserIdentifier(id))
|
||||
err := e.users.Delete(ctx, domain.UserIdentifier(id))
|
||||
if err != nil {
|
||||
c.JSON(ParseServiceError(err))
|
||||
return
|
46
internal/app/api/v1/models/model_options.go
Normal file
46
internal/app/api/v1/models/model_options.go
Normal file
@ -0,0 +1,46 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
type ConfigOption[T any] struct {
|
||||
Value T `json:"Value"`
|
||||
Overridable bool `json:"Overridable,omitempty"`
|
||||
}
|
||||
|
||||
func NewConfigOption[T any](value T, overridable bool) ConfigOption[T] {
|
||||
return ConfigOption[T]{
|
||||
Value: value,
|
||||
Overridable: overridable,
|
||||
}
|
||||
}
|
||||
|
||||
func ConfigOptionFromDomain[T any](opt domain.ConfigOption[T]) ConfigOption[T] {
|
||||
return ConfigOption[T]{
|
||||
Value: opt.Value,
|
||||
Overridable: opt.Overridable,
|
||||
}
|
||||
}
|
||||
|
||||
func ConfigOptionToDomain[T any](opt ConfigOption[T]) domain.ConfigOption[T] {
|
||||
return domain.ConfigOption[T]{
|
||||
Value: opt.Value,
|
||||
Overridable: opt.Overridable,
|
||||
}
|
||||
}
|
||||
|
||||
func StringSliceConfigOptionFromDomain(opt domain.ConfigOption[string]) ConfigOption[[]string] {
|
||||
return ConfigOption[[]string]{
|
||||
Value: internal.SliceString(opt.Value),
|
||||
Overridable: opt.Overridable,
|
||||
}
|
||||
}
|
||||
|
||||
func StringSliceConfigOptionToDomain(opt ConfigOption[[]string]) domain.ConfigOption[string] {
|
||||
return domain.ConfigOption[string]{
|
||||
Value: internal.SliceToString(opt.Value),
|
||||
Overridable: opt.Overridable,
|
||||
}
|
||||
}
|
201
internal/app/api/v1/models/models_interface.go
Normal file
201
internal/app/api/v1/models/models_interface.go
Normal file
@ -0,0 +1,201 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
// Interface represents a WireGuard interface.
|
||||
type Interface struct {
|
||||
// Identifier is the unique identifier of the interface. It is always equal to the device name of the interface.
|
||||
Identifier string `json:"Identifier" example:"wg0" binding:"required"`
|
||||
// DisplayName is a nice display name / description for the interface.
|
||||
DisplayName string `json:"DisplayName" binding:"omitempty,max=64" example:"My Interface"`
|
||||
// Mode is the interface type, either 'server', 'client' or 'any'. The mode specifies how WireGuard Portal handles peers for this interface.
|
||||
Mode string `json:"Mode" example:"server" binding:"required,oneof=server client any"`
|
||||
// PrivateKey is the private key of the interface.
|
||||
PrivateKey string `json:"PrivateKey" example:"gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE=" binding:"required,len=44"`
|
||||
// PublicKey is the public key of the server interface. The public key is used by peers to connect to the server.
|
||||
PublicKey string `json:"PublicKey" example:"HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw=" binding:"required,len=44"`
|
||||
// Disabled is a flag that specifies if the interface is enabled (up) or not (down). Disabled interfaces are not able to accept connections.
|
||||
Disabled bool `json:"Disabled" example:"false"`
|
||||
// DisabledReason is the reason why the interface has been disabled.
|
||||
DisabledReason string `json:"DisabledReason" binding:"required_if=Disabled true" example:"This is a reason why the interface has been disabled."`
|
||||
// SaveConfig is a flag that specifies if the configuration should be saved to the configuration file (wgX.conf in wg-quick format).
|
||||
SaveConfig bool `json:"SaveConfig" example:"false"`
|
||||
|
||||
// ListenPort is the listening port, for example: 51820. The listening port is only required for server interfaces.
|
||||
ListenPort int `json:"ListenPort" binding:"omitempty,min=1,max=65535" example:"51820"`
|
||||
// Addresses is a list of IP addresses (in CIDR format) that are assigned to the interface.
|
||||
Addresses []string `json:"Addresses" binding:"omitempty,dive,cidr" example:"10.11.12.1/24"`
|
||||
// Dns is a list of DNS servers that should be set if the interface is up.
|
||||
Dns []string `json:"Dns" binding:"omitempty,dive,ip" example:"1.1.1.1"`
|
||||
// DnsSearch is the dns search option string that should be set if the interface is up, will be appended to Dns servers.
|
||||
DnsSearch []string `json:"DnsSearch" binding:"omitempty,dive,fqdn" example:"wg.local"`
|
||||
// Mtu is the device MTU of the interface.
|
||||
Mtu int `json:"Mtu" binding:"omitempty,min=1,max=9000" example:"1420"`
|
||||
// FirewallMark is an optional firewall mark which is used to handle interface traffic.
|
||||
FirewallMark uint32 `json:"FirewallMark"`
|
||||
// RoutingTable is an optional routing table which is used to route interface traffic.
|
||||
RoutingTable string `json:"RoutingTable"`
|
||||
|
||||
// PreUp is an optional action that is executed before the device is up.
|
||||
PreUp string `json:"PreUp" example:"echo 'Interface is up'"`
|
||||
// PostUp is an optional action that is executed after the device is up.
|
||||
PostUp string `json:"PostUp" example:"iptables -A FORWARD -i %i -j ACCEPT"`
|
||||
// PreDown is an optional action that is executed before the device is down.
|
||||
PreDown string `json:"PreDown" example:"iptables -D FORWARD -i %i -j ACCEPT"`
|
||||
// PostDown is an optional action that is executed after the device is down.
|
||||
PostDown string `json:"PostDown" example:"echo 'Interface is down'"`
|
||||
|
||||
// PeerDefNetwork specifies the default subnets from which new peers will get their IP addresses. The subnet is specified in CIDR format.
|
||||
PeerDefNetwork []string `json:"PeerDefNetwork" example:"10.11.12.0/24"`
|
||||
// PeerDefDns specifies the default dns servers for a new peer.
|
||||
PeerDefDns []string `json:"PeerDefDns" example:"8.8.8.8"`
|
||||
// PeerDefDnsSearch specifies the default dns search options for a new peer.
|
||||
PeerDefDnsSearch []string `json:"PeerDefDnsSearch" example:"wg.local"`
|
||||
// PeerDefEndpoint specifies the default endpoint for a new peer.
|
||||
PeerDefEndpoint string `json:"PeerDefEndpoint" example:"wg.example.com:51820"`
|
||||
// PeerDefAllowedIPs specifies the default allowed IP addresses for a new peer.
|
||||
PeerDefAllowedIPs []string `json:"PeerDefAllowedIPs" example:"10.11.12.0/24"`
|
||||
// PeerDefMtu specifies the default device MTU for a new peer.
|
||||
PeerDefMtu int `json:"PeerDefMtu" example:"1420"`
|
||||
// PeerDefPersistentKeepalive specifies the default persistent keep-alive value in seconds for a new peer.
|
||||
PeerDefPersistentKeepalive int `json:"PeerDefPersistentKeepalive" example:"25"`
|
||||
// PeerDefFirewallMark specifies the default firewall mark for a new peer.
|
||||
PeerDefFirewallMark uint32 `json:"PeerDefFirewallMark"`
|
||||
// PeerDefRoutingTable specifies the default routing table for a new peer.
|
||||
PeerDefRoutingTable string `json:"PeerDefRoutingTable"`
|
||||
|
||||
// PeerDefPreUp specifies the default action that is executed before the device is up for a new peer.
|
||||
PeerDefPreUp string `json:"PeerDefPreUp"`
|
||||
// PeerDefPostUp specifies the default action that is executed after the device is up for a new peer.
|
||||
PeerDefPostUp string `json:"PeerDefPostUp"`
|
||||
// PeerDefPreDown specifies the default action that is executed before the device is down for a new peer.
|
||||
PeerDefPreDown string `json:"PeerDefPreDown"`
|
||||
// PeerDefPostDown specifies the default action that is executed after the device is down for a new peer.
|
||||
PeerDefPostDown string `json:"PeerDefPostDown"`
|
||||
|
||||
// Calculated values
|
||||
|
||||
// EnabledPeers is the number of enabled peers for this interface. Only enabled peers are able to connect.
|
||||
EnabledPeers int `json:"EnabledPeers" readonly:"true"`
|
||||
// TotalPeers is the total number of peers for this interface.
|
||||
TotalPeers int `json:"TotalPeers" readonly:"true"`
|
||||
}
|
||||
|
||||
func NewInterface(src *domain.Interface, peers []domain.Peer) *Interface {
|
||||
iface := &Interface{
|
||||
Identifier: string(src.Identifier),
|
||||
DisplayName: src.DisplayName,
|
||||
Mode: string(src.Type),
|
||||
PrivateKey: src.PrivateKey,
|
||||
PublicKey: src.PublicKey,
|
||||
Disabled: src.IsDisabled(),
|
||||
DisabledReason: src.DisabledReason,
|
||||
SaveConfig: src.SaveConfig,
|
||||
ListenPort: src.ListenPort,
|
||||
Addresses: domain.CidrsToStringSlice(src.Addresses),
|
||||
Dns: internal.SliceString(src.DnsStr),
|
||||
DnsSearch: internal.SliceString(src.DnsSearchStr),
|
||||
Mtu: src.Mtu,
|
||||
FirewallMark: src.FirewallMark,
|
||||
RoutingTable: src.RoutingTable,
|
||||
PreUp: src.PreUp,
|
||||
PostUp: src.PostUp,
|
||||
PreDown: src.PreDown,
|
||||
PostDown: src.PostDown,
|
||||
PeerDefNetwork: internal.SliceString(src.PeerDefNetworkStr),
|
||||
PeerDefDns: internal.SliceString(src.PeerDefDnsStr),
|
||||
PeerDefDnsSearch: internal.SliceString(src.PeerDefDnsSearchStr),
|
||||
PeerDefEndpoint: src.PeerDefEndpoint,
|
||||
PeerDefAllowedIPs: internal.SliceString(src.PeerDefAllowedIPsStr),
|
||||
PeerDefMtu: src.PeerDefMtu,
|
||||
PeerDefPersistentKeepalive: src.PeerDefPersistentKeepalive,
|
||||
PeerDefFirewallMark: src.PeerDefFirewallMark,
|
||||
PeerDefRoutingTable: src.PeerDefRoutingTable,
|
||||
PeerDefPreUp: src.PeerDefPreUp,
|
||||
PeerDefPostUp: src.PeerDefPostUp,
|
||||
PeerDefPreDown: src.PeerDefPreDown,
|
||||
PeerDefPostDown: src.PeerDefPostDown,
|
||||
|
||||
EnabledPeers: 0,
|
||||
TotalPeers: 0,
|
||||
}
|
||||
|
||||
if len(peers) > 0 {
|
||||
iface.TotalPeers = len(peers)
|
||||
|
||||
activePeers := 0
|
||||
for _, peer := range peers {
|
||||
if !peer.IsDisabled() {
|
||||
activePeers++
|
||||
}
|
||||
}
|
||||
iface.EnabledPeers = activePeers
|
||||
}
|
||||
|
||||
return iface
|
||||
}
|
||||
|
||||
func NewInterfaces(src []domain.Interface, srcPeers [][]domain.Peer) []Interface {
|
||||
results := make([]Interface, len(src))
|
||||
for i := range src {
|
||||
results[i] = *NewInterface(&src[i], srcPeers[i])
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func NewDomainInterface(src *Interface) *domain.Interface {
|
||||
now := time.Now()
|
||||
|
||||
cidrs, _ := domain.CidrsFromArray(src.Addresses)
|
||||
|
||||
res := &domain.Interface{
|
||||
BaseModel: domain.BaseModel{},
|
||||
Identifier: domain.InterfaceIdentifier(src.Identifier),
|
||||
KeyPair: domain.KeyPair{
|
||||
PrivateKey: src.PrivateKey,
|
||||
PublicKey: src.PublicKey,
|
||||
},
|
||||
ListenPort: src.ListenPort,
|
||||
Addresses: cidrs,
|
||||
DnsStr: internal.SliceToString(src.Dns),
|
||||
DnsSearchStr: internal.SliceToString(src.DnsSearch),
|
||||
Mtu: src.Mtu,
|
||||
FirewallMark: src.FirewallMark,
|
||||
RoutingTable: src.RoutingTable,
|
||||
PreUp: src.PreUp,
|
||||
PostUp: src.PostUp,
|
||||
PreDown: src.PreDown,
|
||||
PostDown: src.PostDown,
|
||||
SaveConfig: src.SaveConfig,
|
||||
DisplayName: src.DisplayName,
|
||||
Type: domain.InterfaceType(src.Mode),
|
||||
DriverType: "", // currently unused
|
||||
Disabled: nil, // set below
|
||||
DisabledReason: src.DisabledReason,
|
||||
PeerDefNetworkStr: internal.SliceToString(src.PeerDefNetwork),
|
||||
PeerDefDnsStr: internal.SliceToString(src.PeerDefDns),
|
||||
PeerDefDnsSearchStr: internal.SliceToString(src.PeerDefDnsSearch),
|
||||
PeerDefEndpoint: src.PeerDefEndpoint,
|
||||
PeerDefAllowedIPsStr: internal.SliceToString(src.PeerDefAllowedIPs),
|
||||
PeerDefMtu: src.PeerDefMtu,
|
||||
PeerDefPersistentKeepalive: src.PeerDefPersistentKeepalive,
|
||||
PeerDefFirewallMark: src.PeerDefFirewallMark,
|
||||
PeerDefRoutingTable: src.PeerDefRoutingTable,
|
||||
PeerDefPreUp: src.PeerDefPreUp,
|
||||
PeerDefPostUp: src.PeerDefPostUp,
|
||||
PeerDefPreDown: src.PeerDefPreDown,
|
||||
PeerDefPostDown: src.PeerDefPostDown,
|
||||
}
|
||||
|
||||
if src.Disabled {
|
||||
res.Disabled = &now
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
195
internal/app/api/v1/models/models_peer.go
Normal file
195
internal/app/api/v1/models/models_peer.go
Normal file
@ -0,0 +1,195 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
const ExpiryDateTimeLayout = "\"2006-01-02\""
|
||||
|
||||
type ExpiryDate struct {
|
||||
*time.Time
|
||||
}
|
||||
|
||||
// UnmarshalJSON will unmarshal using 2006-01-02 layout
|
||||
func (d *ExpiryDate) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 0 || string(b) == "null" || string(b) == "\"\"" {
|
||||
return nil
|
||||
}
|
||||
parsed, err := time.Parse(ExpiryDateTimeLayout, string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !parsed.IsZero() {
|
||||
d.Time = &parsed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON will marshal using 2006-01-02 layout
|
||||
func (d *ExpiryDate) MarshalJSON() ([]byte, error) {
|
||||
if d == nil || d.Time == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
|
||||
s := d.Format(ExpiryDateTimeLayout)
|
||||
return []byte(s), nil
|
||||
}
|
||||
|
||||
// Peer represents a WireGuard peer entry.
|
||||
type Peer struct {
|
||||
// Identifier is the unique identifier of the peer. It is always equal to the public key of the peer.
|
||||
Identifier string `json:"Identifier" example:"xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=" binding:"required,len=44"`
|
||||
// DisplayName is a nice display name / description for the peer.
|
||||
DisplayName string `json:"DisplayName" example:"My Peer" binding:"omitempty,max=64"`
|
||||
// UserIdentifier is the identifier of the user that owns the peer.
|
||||
UserIdentifier string `json:"UserIdentifier" example:"uid-1234567"`
|
||||
// InterfaceIdentifier is the identifier of the interface the peer is linked to.
|
||||
InterfaceIdentifier string `json:"InterfaceIdentifier" binding:"required" example:"wg0"`
|
||||
// Disabled is a flag that specifies if the peer is enabled or not. Disabled peers are not able to connect.
|
||||
Disabled bool `json:"Disabled" example:"false"`
|
||||
// DisabledReason is the reason why the peer has been disabled.
|
||||
DisabledReason string `json:"DisabledReason" binding:"required_if=Disabled true" example:"This is a reason why the peer has been disabled."`
|
||||
// ExpiresAt is the expiry date of the peer in YYYY-MM-DD format. An expired peer is not able to connect.
|
||||
ExpiresAt ExpiryDate `json:"ExpiresAt,omitempty" binding:"omitempty,datetime=2006-01-02"`
|
||||
// Notes is a note field for peers.
|
||||
Notes string `json:"Notes" example:"This is a note for the peer."`
|
||||
|
||||
// Endpoint is the endpoint address of the peer.
|
||||
Endpoint ConfigOption[string] `json:"Endpoint"`
|
||||
// EndpointPublicKey is the endpoint public key.
|
||||
EndpointPublicKey ConfigOption[string] `json:"EndpointPublicKey"`
|
||||
// AllowedIPs is a list of allowed IP subnets for the peer.
|
||||
AllowedIPs ConfigOption[[]string] `json:"AllowedIPs"`
|
||||
// ExtraAllowedIPs is a list of additional allowed IP subnets for the peer. These allowed IP subnets are added on the server side.
|
||||
ExtraAllowedIPs []string `json:"ExtraAllowedIPs"`
|
||||
// PresharedKey is the optional pre-shared Key of the peer.
|
||||
PresharedKey string `json:"PresharedKey" example:"yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=" binding:"omitempty,len=44"`
|
||||
// PersistentKeepalive is the optional persistent keep-alive interval in seconds.
|
||||
PersistentKeepalive ConfigOption[int] `json:"PersistentKeepalive" binding:"omitempty,gte=0"`
|
||||
|
||||
// PrivateKey is the private Key of the peer.
|
||||
PrivateKey string `json:"PrivateKey" example:"yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=" binding:"required,len=44"`
|
||||
// PublicKey is the public Key of the server peer.
|
||||
PublicKey string `json:"PublicKey" example:"TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=" binding:"omitempty,len=44"`
|
||||
|
||||
// Mode is the peer interface type (server, client, any).
|
||||
Mode string `json:"Mode" example:"client" binding:"omitempty,oneof=server client any"`
|
||||
|
||||
// Addresses is a list of IP addresses in CIDR format (both IPv4 and IPv6) for the peer.
|
||||
Addresses []string `json:"Addresses" example:"10.11.12.2/24" binding:"omitempty,dive,cidr"`
|
||||
// CheckAliveAddress is an optional ip address or DNS name that is used for ping checks.
|
||||
CheckAliveAddress string `json:"CheckAliveAddress" binding:"omitempty,ip|fqdn" example:"1.1.1.1"`
|
||||
// Dns is a list of DNS servers that should be set if the peer interface is up.
|
||||
Dns ConfigOption[[]string] `json:"Dns"`
|
||||
// DnsSearch is the dns search option string that should be set if the peer interface is up, will be appended to Dns servers.
|
||||
DnsSearch ConfigOption[[]string] `json:"DnsSearch"`
|
||||
// Mtu is the device MTU of the peer.
|
||||
Mtu ConfigOption[int] `json:"Mtu"`
|
||||
// FirewallMark is an optional firewall mark which is used to handle peer traffic.
|
||||
FirewallMark ConfigOption[uint32] `json:"FirewallMark"`
|
||||
// RoutingTable is an optional routing table which is used to route peer traffic.
|
||||
RoutingTable ConfigOption[string] `json:"RoutingTable"`
|
||||
|
||||
// PreUp is an optional action that is executed before the device is up.
|
||||
PreUp ConfigOption[string] `json:"PreUp"`
|
||||
// PostUp is an optional action that is executed after the device is up.
|
||||
PostUp ConfigOption[string] `json:"PostUp"`
|
||||
// PreDown is an optional action that is executed before the device is down.
|
||||
PreDown ConfigOption[string] `json:"PreDown"`
|
||||
// PostDown is an optional action that is executed after the device is down.
|
||||
PostDown ConfigOption[string] `json:"PostDown"`
|
||||
}
|
||||
|
||||
func NewPeer(src *domain.Peer) *Peer {
|
||||
return &Peer{
|
||||
Identifier: string(src.Identifier),
|
||||
DisplayName: src.DisplayName,
|
||||
UserIdentifier: string(src.UserIdentifier),
|
||||
InterfaceIdentifier: string(src.InterfaceIdentifier),
|
||||
Disabled: src.IsDisabled(),
|
||||
DisabledReason: src.DisabledReason,
|
||||
ExpiresAt: ExpiryDate{src.ExpiresAt},
|
||||
Notes: src.Notes,
|
||||
Endpoint: ConfigOptionFromDomain(src.Endpoint),
|
||||
EndpointPublicKey: ConfigOptionFromDomain(src.EndpointPublicKey),
|
||||
AllowedIPs: StringSliceConfigOptionFromDomain(src.AllowedIPsStr),
|
||||
ExtraAllowedIPs: internal.SliceString(src.ExtraAllowedIPsStr),
|
||||
PresharedKey: string(src.PresharedKey),
|
||||
PersistentKeepalive: ConfigOptionFromDomain(src.PersistentKeepalive),
|
||||
PrivateKey: src.Interface.PrivateKey,
|
||||
PublicKey: src.Interface.PublicKey,
|
||||
Mode: string(src.Interface.Type),
|
||||
Addresses: domain.CidrsToStringSlice(src.Interface.Addresses),
|
||||
CheckAliveAddress: src.Interface.CheckAliveAddress,
|
||||
Dns: StringSliceConfigOptionFromDomain(src.Interface.DnsStr),
|
||||
DnsSearch: StringSliceConfigOptionFromDomain(src.Interface.DnsSearchStr),
|
||||
Mtu: ConfigOptionFromDomain(src.Interface.Mtu),
|
||||
FirewallMark: ConfigOptionFromDomain(src.Interface.FirewallMark),
|
||||
RoutingTable: ConfigOptionFromDomain(src.Interface.RoutingTable),
|
||||
PreUp: ConfigOptionFromDomain(src.Interface.PreUp),
|
||||
PostUp: ConfigOptionFromDomain(src.Interface.PostUp),
|
||||
PreDown: ConfigOptionFromDomain(src.Interface.PreDown),
|
||||
PostDown: ConfigOptionFromDomain(src.Interface.PostDown),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPeers(src []domain.Peer) []Peer {
|
||||
results := make([]Peer, len(src))
|
||||
for i := range src {
|
||||
results[i] = *NewPeer(&src[i])
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func NewDomainPeer(src *Peer) *domain.Peer {
|
||||
now := time.Now()
|
||||
|
||||
cidrs, _ := domain.CidrsFromArray(src.Addresses)
|
||||
|
||||
res := &domain.Peer{
|
||||
BaseModel: domain.BaseModel{},
|
||||
Endpoint: ConfigOptionToDomain(src.Endpoint),
|
||||
EndpointPublicKey: ConfigOptionToDomain(src.EndpointPublicKey),
|
||||
AllowedIPsStr: StringSliceConfigOptionToDomain(src.AllowedIPs),
|
||||
ExtraAllowedIPsStr: internal.SliceToString(src.ExtraAllowedIPs),
|
||||
PresharedKey: domain.PreSharedKey(src.PresharedKey),
|
||||
PersistentKeepalive: ConfigOptionToDomain(src.PersistentKeepalive),
|
||||
DisplayName: src.DisplayName,
|
||||
Identifier: domain.PeerIdentifier(src.Identifier),
|
||||
UserIdentifier: domain.UserIdentifier(src.UserIdentifier),
|
||||
InterfaceIdentifier: domain.InterfaceIdentifier(src.InterfaceIdentifier),
|
||||
Disabled: nil, // set below
|
||||
DisabledReason: src.DisabledReason,
|
||||
ExpiresAt: src.ExpiresAt.Time,
|
||||
Notes: src.Notes,
|
||||
Interface: domain.PeerInterfaceConfig{
|
||||
KeyPair: domain.KeyPair{
|
||||
PrivateKey: src.PrivateKey,
|
||||
PublicKey: src.PublicKey,
|
||||
},
|
||||
Type: domain.InterfaceType(src.Mode),
|
||||
Addresses: cidrs,
|
||||
CheckAliveAddress: src.CheckAliveAddress,
|
||||
DnsStr: StringSliceConfigOptionToDomain(src.Dns),
|
||||
DnsSearchStr: StringSliceConfigOptionToDomain(src.DnsSearch),
|
||||
Mtu: ConfigOptionToDomain(src.Mtu),
|
||||
FirewallMark: ConfigOptionToDomain(src.FirewallMark),
|
||||
RoutingTable: ConfigOptionToDomain(src.RoutingTable),
|
||||
PreUp: ConfigOptionToDomain(src.PreUp),
|
||||
PostUp: ConfigOptionToDomain(src.PostUp),
|
||||
PreDown: ConfigOptionToDomain(src.PreDown),
|
||||
PostDown: ConfigOptionToDomain(src.PostDown),
|
||||
},
|
||||
}
|
||||
|
||||
if src.Disabled {
|
||||
res.Disabled = &now
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
@ -8,28 +8,46 @@ import (
|
||||
|
||||
// User represents a user in the system.
|
||||
type User struct {
|
||||
Identifier string `json:"Identifier"` // The unique identifier of the user.
|
||||
Email string `json:"Email"` // The email address of the user. This field is optional.
|
||||
Source string `json:"Source"` // The source of the user. This field is optional.
|
||||
ProviderName string `json:"ProviderName"` // The name of the authentication provider. This field is optional.
|
||||
IsAdmin bool `json:"IsAdmin"` // If this field is set, the user is an admin.
|
||||
// The unique identifier of the user.
|
||||
Identifier string `json:"Identifier" binding:"required,max=64" example:"uid-1234567"`
|
||||
// The email address of the user. This field is optional.
|
||||
Email string `json:"Email" binding:"omitempty,email" example:"test@test.com"`
|
||||
// The source of the user. This field is optional.
|
||||
Source string `json:"Source" binding:"oneof=db" example:"db"`
|
||||
// The name of the authentication provider. This field is read-only.
|
||||
ProviderName string `json:"ProviderName,omitempty" readonly:"true" example:""`
|
||||
// If this field is set, the user is an admin.
|
||||
IsAdmin bool `json:"IsAdmin" binding:"required" example:"false"`
|
||||
|
||||
Firstname string `json:"Firstname"` // The first name of the user. This field is optional.
|
||||
Lastname string `json:"Lastname"` // The last name of the user. This field is optional.
|
||||
Phone string `json:"Phone"` // The phone number of the user. This field is optional.
|
||||
Department string `json:"Department"` // The department of the user. This field is optional.
|
||||
Notes string `json:"Notes"` // Additional notes about the user. This field is optional.
|
||||
// The first name of the user. This field is optional.
|
||||
Firstname string `json:"Firstname" example:"Max"`
|
||||
// The last name of the user. This field is optional.
|
||||
Lastname string `json:"Lastname" example:"Muster"`
|
||||
// The phone number of the user. This field is optional.
|
||||
Phone string `json:"Phone" example:"+1234546789"`
|
||||
// The department of the user. This field is optional.
|
||||
Department string `json:"Department" example:"Software Development"`
|
||||
// Additional notes about the user. This field is optional.
|
||||
Notes string `json:"Notes" example:"some sample notes"`
|
||||
|
||||
Password string `json:"Password,omitempty"` // The password of the user. This field is never populated on read operations.
|
||||
Disabled bool `json:"Disabled"` // If this field is set, the user is disabled.
|
||||
DisabledReason string `json:"DisabledReason"` // The reason why the user has been disabled.
|
||||
Locked bool `json:"Locked"` // If this field is set, the user is locked and thus unable to log in to WireGuard Portal.
|
||||
LockedReason string `json:"LockedReason"` // The reason why the user has been locked.
|
||||
// The password of the user. This field is never populated on read operations.
|
||||
Password string `json:"Password,omitempty" binding:"omitempty,min=16,max=64" example:""`
|
||||
// If this field is set, the user is disabled.
|
||||
Disabled bool `json:"Disabled" example:"false"`
|
||||
// The reason why the user has been disabled.
|
||||
DisabledReason string `json:"DisabledReason" binding:"required_if=Disabled true" example:""`
|
||||
// If this field is set, the user is locked and thus unable to log in to WireGuard Portal.
|
||||
Locked bool `json:"Locked" example:"false"`
|
||||
// The reason why the user has been locked.
|
||||
LockedReason string `json:"LockedReason" binding:"required_if=Locked true" example:""`
|
||||
|
||||
ApiToken string `json:"ApiToken"` // The API token of the user. This field is never populated on bulk read operations.
|
||||
ApiEnabled bool `json:"ApiEnabled"` // If this field is set, the user is allowed to use the RESTful API. This field is read-only.
|
||||
// The API token of the user. This field is never populated on bulk read operations.
|
||||
ApiToken string `json:"ApiToken,omitempty" binding:"omitempty,min=32,max=64" example:""`
|
||||
// If this field is set, the user is allowed to use the RESTful API. This field is read-only.
|
||||
ApiEnabled bool `json:"ApiEnabled" readonly:"true" example:"false"`
|
||||
|
||||
PeerCount int `json:"PeerCount"` // The number of peers linked to the user. This field is read-only.
|
||||
// The number of peers linked to the user. This field is read-only.
|
||||
PeerCount int `json:"PeerCount" readonly:"true" example:"2"`
|
||||
}
|
||||
|
||||
func NewUser(src *domain.User, exposeCredentials bool) *User {
|
||||
|
@ -357,7 +357,7 @@ func (m Manager) CreateInterface(ctx context.Context, in *domain.Interface) (*do
|
||||
return nil, fmt.Errorf("unable to load existing interface %s: %w", in.Identifier, err)
|
||||
}
|
||||
if existingInterface != nil {
|
||||
return nil, fmt.Errorf("interface %s already exists", in.Identifier)
|
||||
return nil, fmt.Errorf("interface %s already exists: %w", in.Identifier, domain.ErrDuplicateEntry)
|
||||
}
|
||||
|
||||
if err := m.validateInterfaceCreation(ctx, existingInterface, in); err != nil {
|
||||
@ -825,6 +825,13 @@ func (m Manager) validateInterfaceCreation(ctx context.Context, old, new *domain
|
||||
return fmt.Errorf("insufficient permissions")
|
||||
}
|
||||
|
||||
// validate public key if it is set
|
||||
if new.PublicKey != "" && new.PrivateKey != "" {
|
||||
if domain.PublicKeyFromPrivateKey(new.PrivateKey) != new.PublicKey {
|
||||
return fmt.Errorf("invalid public key for given privatekey: %w", domain.ErrInvalidData)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -159,7 +159,7 @@ func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee
|
||||
return nil, fmt.Errorf("unable to load existing peer %s: %w", peer.Identifier, err)
|
||||
}
|
||||
if existingPeer != nil {
|
||||
return nil, fmt.Errorf("peer %s already exists", peer.Identifier)
|
||||
return nil, fmt.Errorf("peer %s already exists: %w", peer.Identifier, domain.ErrDuplicateEntry)
|
||||
}
|
||||
|
||||
if err := m.validatePeerCreation(ctx, existingPeer, peer); err != nil {
|
||||
@ -234,6 +234,15 @@ func (m Manager) UpdatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee
|
||||
if existingPeer.Identifier != domain.PeerIdentifier(peer.Interface.PublicKey) {
|
||||
peer.Identifier = domain.PeerIdentifier(peer.Interface.PublicKey) // set new identifier
|
||||
|
||||
// check for already existing peer with new identifier
|
||||
duplicatePeer, err := m.db.GetPeer(ctx, peer.Identifier)
|
||||
if err != nil && !errors.Is(err, domain.ErrNotFound) {
|
||||
return nil, fmt.Errorf("unable to load existing peer %s: %w", peer.Identifier, err)
|
||||
}
|
||||
if duplicatePeer != nil {
|
||||
return nil, fmt.Errorf("peer %s already exists: %w", peer.Identifier, domain.ErrDuplicateEntry)
|
||||
}
|
||||
|
||||
// delete old peer
|
||||
err = m.DeletePeer(ctx, existingPeer.Identifier)
|
||||
if err != nil {
|
||||
@ -431,7 +440,7 @@ func (m Manager) validatePeerModifications(ctx context.Context, old, new *domain
|
||||
currentUser := domain.GetUserInfo(ctx)
|
||||
|
||||
if !currentUser.IsAdmin {
|
||||
return fmt.Errorf("insufficient permissions")
|
||||
return domain.ErrNoPermission
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -441,11 +450,16 @@ func (m Manager) validatePeerCreation(ctx context.Context, old, new *domain.Peer
|
||||
currentUser := domain.GetUserInfo(ctx)
|
||||
|
||||
if new.Identifier == "" {
|
||||
return fmt.Errorf("invalid peer identifier")
|
||||
return fmt.Errorf("invalid peer identifier: %w", domain.ErrInvalidData)
|
||||
}
|
||||
|
||||
if !currentUser.IsAdmin {
|
||||
return fmt.Errorf("insufficient permissions")
|
||||
return domain.ErrNoPermission
|
||||
}
|
||||
|
||||
_, err := m.db.GetInterface(ctx, new.InterfaceIdentifier)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid interface: %w", domain.ErrInvalidData)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -455,7 +469,7 @@ func (m Manager) validatePeerDeletion(ctx context.Context, del *domain.Peer) err
|
||||
currentUser := domain.GetUserInfo(ctx)
|
||||
|
||||
if !currentUser.IsAdmin {
|
||||
return fmt.Errorf("insufficient permissions")
|
||||
return domain.ErrNoPermission
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -127,6 +127,7 @@ func defaultConfig() *Config {
|
||||
cfg.Advanced.ExpiryCheckInterval = 15 * time.Minute
|
||||
cfg.Advanced.RulePrioOffset = 20000
|
||||
cfg.Advanced.RouteTableOffset = 20000
|
||||
cfg.Advanced.ApiAdminOnly = true
|
||||
|
||||
cfg.Statistics.UsePingChecks = true
|
||||
cfg.Statistics.PingCheckWorkers = 10
|
||||
|
Loading…
Reference in New Issue
Block a user