peer config and qr code

This commit is contained in:
Christoph Haas 2023-06-24 01:35:25 +02:00
parent 4a53a5207a
commit 9fdb8d8633
29 changed files with 379 additions and 185 deletions

View File

@ -36,7 +36,7 @@ const title = computed(() => {
watch(() => props.visible, async (newValue, oldValue) => { watch(() => props.visible, async (newValue, oldValue) => {
if (oldValue === false && newValue === true) { // if modal is shown if (oldValue === false && newValue === true) { // if modal is shown
console.log(selectedInterface.value) console.log(selectedInterface.value)
await interfaces.InterfaceConfig(selectedInterface.value.Identifier) await interfaces.LoadInterfaceConfig(selectedInterface.value.Identifier)
configString.value = interfaces.configuration configString.value = interfaces.configuration
} }
} }

View File

@ -9,7 +9,7 @@ import Vue3TagsInput from "vue3-tags-input";
import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators'; import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
import isCidr from "is-cidr"; import isCidr from "is-cidr";
import {isIP} from 'is-ip'; import {isIP} from 'is-ip';
import { freshPeer } from '@/helpers/models'; import { freshPeer, freshInterface } from '@/helpers/models';
const { t } = useI18n() const { t } = useI18n()
@ -31,10 +31,7 @@ const selectedInterface = computed(() => {
let i = interfaces.GetSelected; let i = interfaces.GetSelected;
if (!i) { if (!i) {
i = { // dummy interface to avoid 'undefined' exceptions i = freshInterface() // dummy interface to avoid 'undefined' exceptions
Identifier: "none",
Mode: "server"
}
} }
return i return i

View File

@ -2,7 +2,12 @@
import Modal from "./Modal.vue"; import Modal from "./Modal.vue";
import {peerStore} from "@/stores/peers"; import {peerStore} from "@/stores/peers";
import {interfaceStore} from "@/stores/interfaces"; import {interfaceStore} from "@/stores/interfaces";
import {computed} from "vue"; import {computed, ref, watch} from "vue";
import {useI18n} from "vue-i18n";
import { freshInterface, freshPeer } from '@/helpers/models';
import Prism from "vue-prism-component";
const { t } = useI18n()
const peers = peerStore() const peers = peerStore()
const interfaces = interfaceStore() const interfaces = interfaceStore()
@ -18,30 +23,49 @@ function close() {
emit('close') emit('close')
} }
const configString = ref("")
const selectedPeer = computed(() => { const selectedPeer = computed(() => {
return peers.Find(props.peerId) let p = peers.Find(props.peerId)
if (!p) {
p = freshPeer() // dummy peer to avoid 'undefined' exceptions
}
return p
}) })
const selectedInterface = computed(() => { const selectedInterface = computed(() => {
let i = interfaces.GetSelected; let i = interfaces.GetSelected;
if (!i) { if (!i) {
i = { // dummy interface to avoid 'undefined' exceptions i = freshInterface() // dummy interface to avoid 'undefined' exceptions
Identifier: "none",
Mode: "server"
}
} }
return i return i
}) })
const title = computed(() => { const title = computed(() => {
if (selectedPeer.value) { if (!props.visible) {
return "Peer: " + selectedPeer.value.Name return "" // otherwise interfaces.GetSelected will die...
}
if (selectedInterface.value.Mode === "server") {
return t("interfaces.peer.view") + ": " + selectedPeer.value.DisplayName
} else {
return t("interfaces.endpoint.view") + ": " + selectedPeer.value.DisplayName
} }
return ""
}) })
watch(() => props.visible, async (newValue, oldValue) => {
if (oldValue === false && newValue === true) { // if modal is shown
console.log(selectedInterface.value)
console.log(selectedPeer.value)
await peers.LoadPeerConfig(selectedPeer.value.Identifier)
configString.value = peers.configuration
}
}
)
</script> </script>
<template> <template>
@ -60,16 +84,16 @@ const title = computed(() => {
<div class="col-md-8"> <div class="col-md-8">
<h4>Details</h4> <h4>Details</h4>
<ul> <ul>
<li>Firstname: Some</li> <li>Identifier: {{ selectedPeer.PublicKey }}</li>
<li>Lastname: Username</li> <li>IP Addresses: <span v-for="ip in selectedPeer.Addresses" :key="ip" class="badge rounded-pill bg-light">{{ ip }}</span></li>
<li>Phone: 123456789</li> <li>Linked User: {{ selectedPeer.UserIdentifier }}</li>
<li>Mail: x@y.de</li> <li>Notes: {{ selectedPeer.Notes }}</li>
</ul> </ul>
<h4>Traffic</h4> <h4>Traffic</h4>
<p><i class="fas fa-long-arrow-alt-down"></i> 1.5 MB / <i class="fas fa-long-arrow-alt-up"></i> 3.9 MB</p> <p><i class="fas fa-long-arrow-alt-down"></i> 1.5 MB / <i class="fas fa-long-arrow-alt-up"></i> 3.9 MB</p>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<img class="config-qr-img" src="https://hexdocs.pm/qr_code/docs/qrcode.svg"> <img class="config-qr-img" :src="peers.ConfigQrUrl(props.peerId)" loading="lazy" alt="Configuration QR Code">
</div> </div>
</div> </div>
</div> </div>
@ -83,28 +107,7 @@ const title = computed(() => {
</h2> </h2>
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#accordionExample" style=""> <div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#accordionExample" style="">
<div class="accordion-body"> <div class="accordion-body">
<pre> <Prism language="ini" :code="configString"></Prism>
# AUTOGENERATED FILE - PROVIDED BY WIREGUARD PORTAL
# WireGuard configuration: Some username (Home)
# -WGP- PublicKey: xyz123
[Interface]
# Core settings
PrivateKey = abcd2131234
Address = 10.6.6.3/32, fd9f:6666::3/128
# Misc. settings (optional)
DNS = 10.10.1.20, fd9f:6666::10:6:6:1
MTU = 1380
[Peer]
PublicKey = oidjsfgsp9oih23
Endpoint = vpn.server.de:51820
AllowedIPs = 10.6.6.0/24, 10.10.0.0/16, 10.12.0.0/16, fd9f:6666::/64
PresharedKey = +1FPHPdsfjkln23
PersistentKeepalive = 16
</pre>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,7 @@
export function base64_url_encode(input) {
let output = btoa(input)
output = output.replace('+', '.')
output = output.replace('/', '_')
output = output.replace('=', '-')
return output
}

View File

@ -2,6 +2,7 @@ import { authStore } from '../stores/auth';
import { securityStore } from '../stores/security'; import { securityStore } from '../stores/security';
export const fetchWrapper = { export const fetchWrapper = {
url: apiUrl(),
get: request('GET'), get: request('GET'),
post: request('POST'), post: request('POST'),
put: request('PUT'), put: request('PUT'),
@ -9,6 +10,7 @@ export const fetchWrapper = {
}; };
export const apiWrapper = { export const apiWrapper = {
url: apiUrl(),
get: apiRequest('GET'), get: apiRequest('GET'),
post: apiRequest('POST'), post: apiRequest('POST'),
put: apiRequest('PUT'), put: apiRequest('PUT'),
@ -46,6 +48,13 @@ function apiRequest(method) {
} }
} }
// apiUrl uses WGPORTAL_BACKEND_BASE_URL as base URL
function apiUrl() {
return (path) => {
return WGPORTAL_BACKEND_BASE_URL + path
}
}
// helper functions // helper functions
function getHeaders(method, url) { function getHeaders(method, url) {

View File

@ -54,9 +54,8 @@
"h2-client": "Aktuelle Endpunkte", "h2-client": "Aktuelle Endpunkte",
"tableHeadings": [ "tableHeadings": [
"Name", "Name",
"Kennung",
"Benutzer", "Benutzer",
"IPs", "IP's",
"Endpunkt", "Endpunkt",
"Handschlag" "Handschlag"
], ],

View File

@ -51,9 +51,8 @@
"h2-client": "Current Endpoints", "h2-client": "Current Endpoints",
"tableHeadings": [ "tableHeadings": [
"Name", "Name",
"Identifier",
"User", "User",
"IPs", "IP's",
"Endpoint", "Endpoint",
"Handshake" "Handshake"
], ],

View File

@ -3,6 +3,7 @@ import { defineStore } from 'pinia'
import {apiWrapper} from '@/helpers/fetch-wrapper' import {apiWrapper} from '@/helpers/fetch-wrapper'
import {notify} from "@kyvg/vue3-notification"; import {notify} from "@kyvg/vue3-notification";
import { freshInterface } from '@/helpers/models'; import { freshInterface } from '@/helpers/models';
import { base64_url_encode } from '@/helpers/encoding';
const baseUrl = `/interface` const baseUrl = `/interface`
@ -66,11 +67,11 @@ export const interfaceStore = defineStore({
}) })
}) })
}, },
async InterfaceConfig(id) { async LoadInterfaceConfig(id) {
return apiWrapper.get(`${baseUrl}/config/${encodeURIComponent(id)}`) return apiWrapper.get(`${baseUrl}/config/${base64_url_encode(id)}`)
.then(this.setInterfaceConfig) .then(this.setInterfaceConfig)
.catch(error => { .catch(error => {
this.prepared = {} this.configuration = ""
console.log("Failed to load interface configuration: ", error) console.log("Failed to load interface configuration: ", error)
notify({ notify({
title: "Backend Connection Failure", title: "Backend Connection Failure",
@ -80,7 +81,7 @@ export const interfaceStore = defineStore({
}, },
async DeleteInterface(id) { async DeleteInterface(id) {
this.fetching = true this.fetching = true
return apiWrapper.delete(`${baseUrl}/${encodeURIComponent(id)}`) return apiWrapper.delete(`${baseUrl}/${base64_url_encode(id)}`)
.then(() => { .then(() => {
this.interfaces = this.interfaces.filter(i => i.Identifier !== id) this.interfaces = this.interfaces.filter(i => i.Identifier !== id)
if (this.interfaces.length > 0) { if (this.interfaces.length > 0) {
@ -98,7 +99,7 @@ export const interfaceStore = defineStore({
}, },
async UpdateInterface(id, formData) { async UpdateInterface(id, formData) {
this.fetching = true this.fetching = true
return apiWrapper.put(`${baseUrl}/${encodeURIComponent(id)}`, formData) return apiWrapper.put(`${baseUrl}/${base64_url_encode(id)}`, formData)
.then(iface => { .then(iface => {
let idx = this.interfaces.findIndex((i) => i.Identifier === id) let idx = this.interfaces.findIndex((i) => i.Identifier === id)
this.interfaces[idx] = iface this.interfaces[idx] = iface

View File

@ -3,6 +3,7 @@ import {apiWrapper} from "../helpers/fetch-wrapper";
import {notify} from "@kyvg/vue3-notification"; import {notify} from "@kyvg/vue3-notification";
import {interfaceStore} from "./interfaces"; import {interfaceStore} from "./interfaces";
import { freshPeer } from '@/helpers/models'; import { freshPeer } from '@/helpers/models';
import { base64_url_encode } from '@/helpers/encoding';
const baseUrl = `/peer` const baseUrl = `/peer`
@ -12,6 +13,7 @@ export const peerStore = defineStore({
peers: [], peers: [],
peer: freshPeer(), peer: freshPeer(),
prepared: freshPeer(), prepared: freshPeer(),
configuration: "",
filter: "", filter: "",
pageSize: 10, pageSize: 10,
pageOffset: 0, pageOffset: 0,
@ -37,6 +39,9 @@ export const peerStore = defineStore({
FilteredAndPaged: (state) => { FilteredAndPaged: (state) => {
return state.Filtered.slice(state.pageOffset, state.pageOffset + state.pageSize) return state.Filtered.slice(state.pageOffset, state.pageOffset + state.pageSize)
}, },
ConfigQrUrl: (state) => {
return (id) => apiWrapper.url(`${baseUrl}/config-qr/${base64_url_encode(id)}`)
},
isFetching: (state) => state.fetching, isFetching: (state) => state.fetching,
hasNextPage: (state) => state.pageOffset < (state.FilteredCount - state.pageSize), hasNextPage: (state) => state.pageOffset < (state.FilteredCount - state.pageSize),
hasPrevPage: (state) => state.pageOffset > 0, hasPrevPage: (state) => state.pageOffset > 0,
@ -82,8 +87,11 @@ export const peerStore = defineStore({
setPreparedPeer(peer) { setPreparedPeer(peer) {
this.prepared = peer; this.prepared = peer;
}, },
setPeerConfig(config) {
this.configuration = config;
},
async PreparePeer(interfaceId) { async PreparePeer(interfaceId) {
return apiWrapper.get(`${baseUrl}/iface/${encodeURIComponent(interfaceId)}/prepare`) return apiWrapper.get(`${baseUrl}/iface/${base64_url_encode(interfaceId)}/prepare`)
.then(this.setPreparedPeer) .then(this.setPreparedPeer)
.catch(error => { .catch(error => {
this.prepared = freshPeer() this.prepared = freshPeer()
@ -94,9 +102,21 @@ export const peerStore = defineStore({
}) })
}) })
}, },
async LoadPeerConfig(id) {
return apiWrapper.get(`${baseUrl}/config/${base64_url_encode(id)}`)
.then(this.setPeerConfig)
.catch(error => {
this.configuration = ""
console.log("Failed to load peer configuration: ", error)
notify({
title: "Backend Connection Failure",
text: "Failed to load peer configuration!",
})
})
},
async LoadPeer(id) { async LoadPeer(id) {
this.fetching = true this.fetching = true
return apiWrapper.get(`${baseUrl}/${encodeURIComponent(id)}`) return apiWrapper.get(`${baseUrl}/${base64_url_encode(id)}`)
.then(this.setPeer) .then(this.setPeer)
.catch(error => { .catch(error => {
this.setPeers([]) this.setPeers([])
@ -109,7 +129,7 @@ export const peerStore = defineStore({
}, },
async DeletePeer(id) { async DeletePeer(id) {
this.fetching = true this.fetching = true
return apiWrapper.delete(`${baseUrl}/${encodeURIComponent(id)}`) return apiWrapper.delete(`${baseUrl}/${base64_url_encode(id)}`)
.then(() => { .then(() => {
this.peers = this.peers.filter(p => p.Identifier !== id) this.peers = this.peers.filter(p => p.Identifier !== id)
this.fetching = false this.fetching = false
@ -122,7 +142,7 @@ export const peerStore = defineStore({
}, },
async UpdatePeer(id, formData) { async UpdatePeer(id, formData) {
this.fetching = true this.fetching = true
return apiWrapper.put(`${baseUrl}/${encodeURIComponent(id)}`, formData) return apiWrapper.put(`${baseUrl}/${base64_url_encode(id)}`, formData)
.then(peer => { .then(peer => {
let idx = this.peers.findIndex((p) => p.Identifier === id) let idx = this.peers.findIndex((p) => p.Identifier === id)
this.peers[idx] = peer this.peers[idx] = peer
@ -136,7 +156,7 @@ export const peerStore = defineStore({
}, },
async CreatePeer(interfaceId, formData) { async CreatePeer(interfaceId, formData) {
this.fetching = true this.fetching = true
return apiWrapper.post(`${baseUrl}/iface/${encodeURIComponent(interfaceId)}/new`, formData) return apiWrapper.post(`${baseUrl}/iface/${base64_url_encode(interfaceId)}/new`, formData)
.then(peer => { .then(peer => {
this.peers.push(peer) this.peers.push(peer)
this.fetching = false this.fetching = false
@ -157,7 +177,7 @@ export const peerStore = defineStore({
} }
this.fetching = true this.fetching = true
return apiWrapper.get(`${baseUrl}/iface/${encodeURIComponent(interfaceId)}/all`) return apiWrapper.get(`${baseUrl}/iface/${base64_url_encode(interfaceId)}/all`)
.then(this.setPeers) .then(this.setPeers)
.catch(error => { .catch(error => {
this.setPeers([]) this.setPeers([])

View File

@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
import {apiWrapper} from "@/helpers/fetch-wrapper"; import {apiWrapper} from "@/helpers/fetch-wrapper";
import {notify} from "@kyvg/vue3-notification"; import {notify} from "@kyvg/vue3-notification";
import {authStore} from "@/stores/auth"; import {authStore} from "@/stores/auth";
import { base64_url_encode } from '@/helpers/encoding';
const baseUrl = `/user` const baseUrl = `/user`
@ -79,7 +80,7 @@ export const profileStore = defineStore({
async LoadPeers() { async LoadPeers() {
this.fetching = true this.fetching = true
let currentUser = authStore().user.Identifier let currentUser = authStore().user.Identifier
return apiWrapper.get(`${baseUrl}/${encodeURIComponent(currentUser)}/peers`) return apiWrapper.get(`${baseUrl}/${base64_url_encode(currentUser)}/peers`)
.then(this.setPeers) .then(this.setPeers)
.catch(error => { .catch(error => {
this.setPeers([]) this.setPeers([])
@ -93,7 +94,7 @@ export const profileStore = defineStore({
async LoadUser() { async LoadUser() {
this.fetching = true this.fetching = true
let currentUser = authStore().user.Identifier let currentUser = authStore().user.Identifier
return apiWrapper.get(`${baseUrl}/${encodeURIComponent(currentUser)}`) return apiWrapper.get(`${baseUrl}/${base64_url_encode(currentUser)}`)
.then(this.setUser) .then(this.setUser)
.catch(error => { .catch(error => {
this.setUser({}) this.setUser({})

View File

@ -1,6 +1,7 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import {apiWrapper} from "@/helpers/fetch-wrapper"; import {apiWrapper} from "@/helpers/fetch-wrapper";
import {notify} from "@kyvg/vue3-notification"; import {notify} from "@kyvg/vue3-notification";
import { base64_url_encode } from '@/helpers/encoding';
const baseUrl = `/user` const baseUrl = `/user`
@ -91,7 +92,7 @@ export const userStore = defineStore({
}, },
async DeleteUser(id) { async DeleteUser(id) {
this.fetching = true this.fetching = true
return apiWrapper.delete(`${baseUrl}/${encodeURIComponent(id)}`) return apiWrapper.delete(`${baseUrl}/${base64_url_encode(id)}`)
.then(() => { .then(() => {
this.users = this.users.filter(u => u.Identifier !== id) this.users = this.users.filter(u => u.Identifier !== id)
this.fetching = false this.fetching = false
@ -104,7 +105,7 @@ export const userStore = defineStore({
}, },
async UpdateUser(id, formData) { async UpdateUser(id, formData) {
this.fetching = true this.fetching = true
return apiWrapper.put(`${baseUrl}/${encodeURIComponent(id)}`, formData) return apiWrapper.put(`${baseUrl}/${base64_url_encode(id)}`, formData)
.then(user => { .then(user => {
let idx = this.users.findIndex((u) => u.Identifier === id) let idx = this.users.findIndex((u) => u.Identifier === id)
this.users[idx] = user this.users[idx] = user
@ -131,7 +132,7 @@ export const userStore = defineStore({
}, },
async LoadUserPeers(id) { async LoadUserPeers(id) {
this.fetching = true this.fetching = true
return apiWrapper.get(`${baseUrl}/${encodeURIComponent(id)}/peers`) return apiWrapper.get(`${baseUrl}/${base64_url_encode(id)}/peers`)
.then(this.setUserPeers) .then(this.setUserPeers)
.catch(error => { .catch(error => {
this.setUserPeers([]) this.setUserPeers([])

View File

@ -272,9 +272,8 @@ onMounted(async () => {
<th scope="col">{{ $t('interfaces.tableHeadings[0]') }}</th> <th scope="col">{{ $t('interfaces.tableHeadings[0]') }}</th>
<th scope="col">{{ $t('interfaces.tableHeadings[1]') }}</th> <th scope="col">{{ $t('interfaces.tableHeadings[1]') }}</th>
<th scope="col">{{ $t('interfaces.tableHeadings[2]') }}</th> <th scope="col">{{ $t('interfaces.tableHeadings[2]') }}</th>
<th scope="col">{{ $t('interfaces.tableHeadings[3]') }}</th> <th v-if="interfaces.GetSelected.Mode==='client'" scope="col">{{ $t('interfaces.tableHeadings[3]') }}</th>
<th v-if="interfaces.GetSelected.Mode==='client'" scope="col">{{ $t('interfaces.tableHeadings[4]') }}</th> <th scope="col">{{ $t('interfaces.tableHeadings[4]') }}</th>
<th scope="col">{{ $t('interfaces.tableHeadings[5]') }}</th>
<th scope="col"></th><!-- Actions --> <th scope="col"></th><!-- Actions -->
</tr> </tr>
</thead> </thead>
@ -284,10 +283,9 @@ onMounted(async () => {
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value=""> <input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
</th> </th>
<td>{{peer.DisplayName}}</td> <td>{{peer.DisplayName}}</td>
<td>{{peer.Identifier}}</td>
<td>{{peer.UserIdentifier}}</td> <td>{{peer.UserIdentifier}}</td>
<td> <td>
<span v-for="ip in peer.Addresses" :key="ip" class="badge rounded-pill bg-light">{{ ip }}</span> <span v-for="ip in peer.Addresses" :key="ip" class="badge bg-light me-1">{{ ip }}</span>
</td> </td>
<td v-if="interfaces.GetSelected.Mode==='client'">{{peer.Endpoint.Value}}</td> <td v-if="interfaces.GetSelected.Mode==='client'">{{peer.Endpoint.Value}}</td>
<td>{{peer.LastConnected}}</td> <td>{{peer.LastConnected}}</td>

7
go.mod
View File

@ -19,6 +19,8 @@ require (
github.com/vardius/message-bus v1.1.5 github.com/vardius/message-bus v1.1.5
github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netlink v1.1.0
github.com/xhit/go-simple-mail/v2 v2.13.0 github.com/xhit/go-simple-mail/v2 v2.13.0
github.com/yeqown/go-qrcode/v2 v2.2.1
github.com/yeqown/go-qrcode/writer/standard v1.2.1
golang.org/x/crypto v0.10.0 golang.org/x/crypto v0.10.0
golang.org/x/oauth2 v0.9.0 golang.org/x/oauth2 v0.9.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
@ -37,6 +39,7 @@ require (
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dchest/uniuri v1.2.0 // indirect github.com/dchest/uniuri v1.2.0 // indirect
github.com/fogleman/gg v1.3.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
@ -53,6 +56,7 @@ require (
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
@ -79,6 +83,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect
github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/objx v0.5.0 // indirect
@ -86,7 +91,9 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ugorji/go/codec v1.2.11 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect
golang.org/x/arch v0.3.0 // indirect golang.org/x/arch v0.3.0 // indirect
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
golang.org/x/net v0.11.0 // indirect golang.org/x/net v0.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.9.0 // indirect golang.org/x/sys v0.9.0 // indirect

14
go.sum
View File

@ -28,6 +28,8 @@ github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g=
github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
@ -89,6 +91,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@ -190,6 +194,8 @@ github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZ
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus-community/pro-bing v0.2.0 h1:hyK7yPFndU3LCDwEQJwPQUCjNkp1DGP/VxyzrWfXZUU= github.com/prometheus-community/pro-bing v0.2.0 h1:hyK7yPFndU3LCDwEQJwPQUCjNkp1DGP/VxyzrWfXZUU=
@ -242,6 +248,12 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xhit/go-simple-mail/v2 v2.13.0 h1:OANWU9jHZrVfBkNkvLf8Ww0fexwpQVF/v/5f96fFTLI= github.com/xhit/go-simple-mail/v2 v2.13.0 h1:OANWU9jHZrVfBkNkvLf8Ww0fexwpQVF/v/5f96fFTLI=
github.com/xhit/go-simple-mail/v2 v2.13.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98= github.com/xhit/go-simple-mail/v2 v2.13.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
github.com/yeqown/go-qrcode/v2 v2.2.1 h1:Jc1Q916fwC05R8C7mpWDbrT9tyLPaLLKDABoC5XBCe8=
github.com/yeqown/go-qrcode/v2 v2.2.1/go.mod h1:2Qsk2APUCPne0TsRo40DIkI5MYnbzYKCnKGEFWrxd24=
github.com/yeqown/go-qrcode/writer/standard v1.2.1 h1:FMRZiur5yApUIe4fqtqmcdl/XQTZAZWt2DhkPx4VIW0=
github.com/yeqown/go-qrcode/writer/standard v1.2.1/go.mod h1:ZelyDFiVymrauRjUn454iF7bjsabmB1vixkDA5kq2bw=
github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0=
github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
@ -255,6 +267,8 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=

View File

@ -380,7 +380,7 @@ func (r *SqlRepo) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domai
func (r *SqlRepo) GetInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.Peer, error) { func (r *SqlRepo) GetInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.Peer, error) {
var peers []domain.Peer var peers []domain.Peer
err := r.db.WithContext(ctx).Where("interface_identifier = ?", id).Find(&peers).Error err := r.db.WithContext(ctx).Preload("Addresses").Where("interface_identifier = ?", id).Find(&peers).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@
let WGPORTAL_BACKEND_BASE_URL="http://localhost:5000/api/v0"; let WGPORTAL_BACKEND_BASE_URL="http://localhost:5000/api/v0";
</script> </script>
<script src="/api/v0/config/frontend.js"></script> <script src="/api/v0/config/frontend.js"></script>
<script type="module" crossorigin src="/app/assets/index-2321088d.js"></script> <script type="module" crossorigin src="/app/assets/index-8f53e6dd.js"></script>
<link rel="stylesheet" href="/app/assets/index-a233ff7e.css"> <link rel="stylesheet" href="/app/assets/index-a233ff7e.css">
</head> </head>
<body class="d-flex flex-column min-vh-100"> <body class="d-flex flex-column min-vh-100">

View File

@ -0,0 +1,15 @@
package handlers
import (
"encoding/base64"
"strings"
)
func Base64UrlDecode(in string) string {
in = strings.ReplaceAll(in, "-", "=")
in = strings.ReplaceAll(in, "_", "/")
in = strings.ReplaceAll(in, ".", "+")
output, _ := base64.StdEncoding.DecodeString(in)
return string(output)
}

View File

@ -90,7 +90,7 @@ func (e interfaceEndpoint) handleAllGet() gin.HandlerFunc {
// @Router /interface/get/{id} [get] // @Router /interface/get/{id} [get]
func (e interfaceEndpoint) handleSingleGet() gin.HandlerFunc { func (e interfaceEndpoint) handleSingleGet() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
id := c.Param("id") id := Base64UrlDecode(c.Param("id"))
if id == "" { if id == "" {
c.JSON(http.StatusBadRequest, model.Error{ c.JSON(http.StatusBadRequest, model.Error{
Code: http.StatusInternalServerError, Message: "missing id parameter", Code: http.StatusInternalServerError, Message: "missing id parameter",
@ -122,7 +122,7 @@ func (e interfaceEndpoint) handleSingleGet() gin.HandlerFunc {
// @Router /interface/config/{id} [get] // @Router /interface/config/{id} [get]
func (e interfaceEndpoint) handleConfigGet() gin.HandlerFunc { func (e interfaceEndpoint) handleConfigGet() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
id := c.Param("id") id := Base64UrlDecode(c.Param("id"))
if id == "" { if id == "" {
c.JSON(http.StatusBadRequest, model.Error{ c.JSON(http.StatusBadRequest, model.Error{
Code: http.StatusInternalServerError, Message: "missing id parameter", Code: http.StatusInternalServerError, Message: "missing id parameter",
@ -166,7 +166,7 @@ func (e interfaceEndpoint) handleUpdatePut() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c) ctx := domain.SetUserInfoFromGin(c)
id := c.Param("id") id := Base64UrlDecode(c.Param("id"))
if id == "" { if id == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing interface id"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing interface id"})
return return
@ -241,7 +241,9 @@ func (e interfaceEndpoint) handleCreatePost() gin.HandlerFunc {
// @Router /interface/peers/{id} [get] // @Router /interface/peers/{id} [get]
func (e interfaceEndpoint) handlePeersGet() gin.HandlerFunc { func (e interfaceEndpoint) handlePeersGet() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
id := c.Param("id") ctx := domain.SetUserInfoFromGin(c)
id := Base64UrlDecode(c.Param("id"))
if id == "" { if id == "" {
c.JSON(http.StatusBadRequest, model.Error{ c.JSON(http.StatusBadRequest, model.Error{
Code: http.StatusInternalServerError, Message: "missing id parameter", Code: http.StatusInternalServerError, Message: "missing id parameter",
@ -249,7 +251,7 @@ func (e interfaceEndpoint) handlePeersGet() gin.HandlerFunc {
return return
} }
_, peers, err := e.app.GetInterfaceAndPeers(c.Request.Context(), domain.InterfaceIdentifier(id)) _, peers, err := e.app.GetInterfaceAndPeers(ctx, domain.InterfaceIdentifier(id))
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{ c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(), Code: http.StatusInternalServerError, Message: err.Error(),
@ -276,7 +278,7 @@ func (e interfaceEndpoint) handleDelete() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c) ctx := domain.SetUserInfoFromGin(c)
id := c.Param("id") id := Base64UrlDecode(c.Param("id"))
if id == "" { if id == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing interface id"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing interface id"})
return return

View File

@ -5,6 +5,7 @@ import (
"github.com/h44z/wg-portal/internal/app" "github.com/h44z/wg-portal/internal/app"
"github.com/h44z/wg-portal/internal/app/api/v0/model" "github.com/h44z/wg-portal/internal/app/api/v0/model"
"github.com/h44z/wg-portal/internal/domain" "github.com/h44z/wg-portal/internal/domain"
"io"
"net/http" "net/http"
) )
@ -23,6 +24,8 @@ func (e peerEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti
apiGroup.GET("/iface/:iface/all", e.handleAllGet()) apiGroup.GET("/iface/:iface/all", e.handleAllGet())
apiGroup.GET("/iface/:iface/prepare", e.handlePrepareGet()) apiGroup.GET("/iface/:iface/prepare", e.handlePrepareGet())
apiGroup.POST("/iface/:iface/new", e.handleCreatePost()) apiGroup.POST("/iface/:iface/new", e.handleCreatePost())
apiGroup.GET("/config-qr/:id", e.handleQrCodeGet())
apiGroup.GET("/config/:id", e.handleConfigGet())
apiGroup.GET("/:id", e.handleSingleGet()) apiGroup.GET("/:id", e.handleSingleGet())
apiGroup.PUT("/:id", e.handleUpdatePut()) apiGroup.PUT("/:id", e.handleUpdatePut())
apiGroup.DELETE("/:id", e.handleDelete()) apiGroup.DELETE("/:id", e.handleDelete())
@ -43,7 +46,7 @@ func (e peerEndpoint) handleAllGet() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c) ctx := domain.SetUserInfoFromGin(c)
interfaceId := c.Param("iface") interfaceId := Base64UrlDecode(c.Param("iface"))
if interfaceId == "" { if interfaceId == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing iface parameter"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing iface parameter"})
return return
@ -74,7 +77,7 @@ func (e peerEndpoint) handleSingleGet() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c) ctx := domain.SetUserInfoFromGin(c)
peerId := c.Param("id") peerId := Base64UrlDecode(c.Param("id"))
if peerId == "" { if peerId == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing id parameter"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing id parameter"})
return return
@ -105,7 +108,7 @@ func (e peerEndpoint) handlePrepareGet() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c) ctx := domain.SetUserInfoFromGin(c)
interfaceId := c.Param("iface") interfaceId := Base64UrlDecode(c.Param("iface"))
if interfaceId == "" { if interfaceId == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing iface parameter"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing iface parameter"})
return return
@ -137,7 +140,7 @@ func (e peerEndpoint) handleCreatePost() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c) ctx := domain.SetUserInfoFromGin(c)
interfaceId := c.Param("iface") interfaceId := Base64UrlDecode(c.Param("iface"))
if interfaceId == "" { if interfaceId == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing iface parameter"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing iface parameter"})
return return
@ -176,12 +179,12 @@ func (e peerEndpoint) handleCreatePost() gin.HandlerFunc {
// @Success 200 {object} model.Peer // @Success 200 {object} model.Peer
// @Failure 400 {object} model.Error // @Failure 400 {object} model.Error
// @Failure 500 {object} model.Error // @Failure 500 {object} model.Error
// @Router /peer/{id} [post] // @Router /peer/{id} [put]
func (e peerEndpoint) handleUpdatePut() gin.HandlerFunc { func (e peerEndpoint) handleUpdatePut() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c) ctx := domain.SetUserInfoFromGin(c)
peerId := c.Param("id") peerId := Base64UrlDecode(c.Param("id"))
if peerId == "" { if peerId == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing id parameter"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing id parameter"})
return return
@ -224,7 +227,7 @@ func (e peerEndpoint) handleDelete() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c) ctx := domain.SetUserInfoFromGin(c)
id := c.Param("id") id := Base64UrlDecode(c.Param("id"))
if id == "" { if id == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing peer id"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing peer id"})
return return
@ -239,3 +242,83 @@ func (e peerEndpoint) handleDelete() gin.HandlerFunc {
c.Status(http.StatusNoContent) c.Status(http.StatusNoContent)
} }
} }
// handleConfigGet returns a gorm handler function.
//
// @ID peers_handleConfigGet
// @Tags Peer
// @Summary Get peer configuration as string.
// @Produce json
// @Success 200 {object} string
// @Failure 400 {object} model.Error
// @Failure 500 {object} model.Error
// @Router /peer/config/{id} [get]
func (e peerEndpoint) handleConfigGet() gin.HandlerFunc {
return func(c *gin.Context) {
id := Base64UrlDecode(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, model.Error{
Code: http.StatusInternalServerError, Message: "missing id parameter",
})
return
}
config, err := e.app.GetPeerConfig(c.Request.Context(), domain.PeerIdentifier(id))
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(),
})
return
}
configString, err := io.ReadAll(config)
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, string(configString))
}
}
// handleQrCodeGet returns a gorm handler function.
//
// @ID peers_handleQrCodeGet
// @Tags Peer
// @Summary Get peer configuration as qr code.
// @Produce json
// @Success 200 {object} string
// @Failure 400 {object} model.Error
// @Failure 500 {object} model.Error
// @Router /peer/config-qr/{id} [get]
func (e peerEndpoint) handleQrCodeGet() gin.HandlerFunc {
return func(c *gin.Context) {
id := Base64UrlDecode(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, model.Error{
Code: http.StatusInternalServerError, Message: "missing id parameter",
})
return
}
config, err := e.app.GetPeerConfigQrCode(c.Request.Context(), domain.PeerIdentifier(id))
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(),
})
return
}
configData, err := io.ReadAll(config)
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{
Code: http.StatusInternalServerError, Message: err.Error(),
})
return
}
c.Data(http.StatusOK, "image/png", configData)
}
}

View File

@ -39,7 +39,9 @@ func (e userEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti
// @Router /user/all [get] // @Router /user/all [get]
func (e userEndpoint) handleAllGet() gin.HandlerFunc { func (e userEndpoint) handleAllGet() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
users, err := e.app.GetAllUsers(c.Request.Context()) ctx := domain.SetUserInfoFromGin(c)
users, err := e.app.GetAllUsers(ctx)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return return
@ -63,7 +65,7 @@ func (e userEndpoint) handleSingleGet() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c) ctx := domain.SetUserInfoFromGin(c)
id := c.Param("id") id := Base64UrlDecode(c.Param("id"))
if id == "" { if id == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing user id"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing user id"})
return return
@ -95,7 +97,7 @@ func (e userEndpoint) handleUpdatePut() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c) ctx := domain.SetUserInfoFromGin(c)
id := c.Param("id") id := Base64UrlDecode(c.Param("id"))
if id == "" { if id == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing user id"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing user id"})
return return
@ -166,13 +168,15 @@ func (e userEndpoint) handleCreatePost() gin.HandlerFunc {
// @Router /user/{id}/peers [get] // @Router /user/{id}/peers [get]
func (e userEndpoint) handlePeersGet() gin.HandlerFunc { func (e userEndpoint) handlePeersGet() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
interfaceId := c.Param("id") ctx := domain.SetUserInfoFromGin(c)
interfaceId := Base64UrlDecode(c.Param("id"))
if interfaceId == "" { if interfaceId == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusInternalServerError, Message: "missing id parameter"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusInternalServerError, Message: "missing id parameter"})
return return
} }
peers, err := e.app.GetUserPeers(c.Request.Context(), domain.UserIdentifier(interfaceId)) peers, err := e.app.GetUserPeers(ctx, domain.UserIdentifier(interfaceId))
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return return
@ -197,7 +201,7 @@ func (e userEndpoint) handleDelete() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c) ctx := domain.SetUserInfoFromGin(c)
id := c.Param("id") id := Base64UrlDecode(c.Param("id"))
if id == "" { if id == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing user id"}) c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing user id"})
return return

View File

@ -1,10 +1,13 @@
package filetemplate package filetemplate
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"github.com/h44z/wg-portal/internal/config" "github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain" "github.com/h44z/wg-portal/internal/domain"
"github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard"
"io" "io"
) )
@ -50,3 +53,41 @@ func (m Manager) GetPeerConfig(ctx context.Context, id domain.PeerIdentifier) (i
return m.tplHandler.GetPeerConfig(peer) return m.tplHandler.GetPeerConfig(peer)
} }
func (m Manager) GetPeerConfigQrCode(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error) {
peer, err := m.wg.GetPeer(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to fetch peer %s: %w", id, err)
}
cfgData, err := m.tplHandler.GetPeerConfig(peer)
if err != nil {
return nil, fmt.Errorf("failed to get peer config for %s: %w", id, err)
}
configBytes, err := io.ReadAll(cfgData)
if err != nil {
return nil, fmt.Errorf("failed to read peer config for %s: %w", id, err)
}
code, err := qrcode.New(string(configBytes))
if err != nil {
return nil, fmt.Errorf("failed to initializeqr code for %s: %w", id, err)
}
buf := bytes.NewBuffer(nil)
wr := nopCloser{Writer: buf}
qrWriter := standard.NewWithWriter(wr, standard.WithQRWidth(40), standard.WithBuiltinImageEncoder(standard.PNG_FORMAT))
err = code.Save(qrWriter)
if err != nil {
return nil, fmt.Errorf("failed to write code for %s: %w", id, err)
}
return buf, nil
}
type nopCloser struct {
io.Writer
}
func (nopCloser) Close() error { return nil }

View File

@ -1,11 +1,16 @@
# AUTOGENERATED FILE - DO NOT EDIT # AUTOGENERATED FILE - DO NOT EDIT
# This file uses wg-quick format. See https://man7.org/linux/man-pages/man8/wg-quick.8.html#CONFIGURATION # This file uses wg-quick format.
# See https://man7.org/linux/man-pages/man8/wg-quick.8.html#CONFIGURATION
# Lines starting with the -WGP- tag are used by
# the WireGuard Portal configuration parser.
# -WGP- WIREGUARD PORTAL CONFIGURATION FILE, version {{ .Portal.Version }} # -WGP- WIREGUARD PORTAL CONFIGURATION FILE
# Lines starting with the -WGP- tag are used by the WireGuard Portal configuration parser. # -WGP- version {{ .Portal.Version }}
[Interface] [Interface]
# -WGP- Interface: {{ .Interface.Identifier }} | Updated: {{ .Interface.UpdatedAt }} | Created: {{ .Interface.CreatedAt }} # -WGP- Interface: {{ .Interface.Identifier }}
# -WGP- Created: {{ .Interface.CreatedAt }}
# -WGP- Updated: {{ .Interface.UpdatedAt }}
# -WGP- Display name: {{ .Interface.DisplayName }} # -WGP- Display name: {{ .Interface.DisplayName }}
# -WGP- Interface mode: {{ .Interface.Type }} # -WGP- Interface mode: {{ .Interface.Type }}
# -WGP- PublicKey = {{ .Interface.KeyPair.PublicKey }} # -WGP- PublicKey = {{ .Interface.KeyPair.PublicKey }}
@ -53,30 +58,32 @@ PostDown = {{ .Interface.PostDown }}
# #
{{range .Peers}} {{range .Peers}}
{{- if not .DisabledAt}} {{- if not .IsDisabled}}
[Peer] [Peer]
# -WGP- Peer: {{.Uid}} | Updated: {{.UpdatedAt}} | Created: {{.CreatedAt}} # -WGP- Peer: {{.Identifier}}
# -WGP- Display name: {{ .Identifier }} # -WGP- Created: {{.CreatedAt}}
{{- if .KeyPair.PrivateKey}} # -WGP- Updated: {{.UpdatedAt}}
# -WGP- PrivateKey: {{.KeyPair.PrivateKey}} # -WGP- Display name: {{ .DisplayName }}
{{- if .Interface.KeyPair.PrivateKey}}
# -WGP- PrivateKey: {{.Interface.KeyPair.PrivateKey}}
{{- end}} {{- end}}
PublicKey = {{ .KeyPair.PublicKey }} PublicKey = {{ .Interface.KeyPair.PublicKey }}
{{- if .PresharedKey}} {{- if .PresharedKey}}
PresharedKey = {{ .PresharedKey }} PresharedKey = {{ .PresharedKey }}
{{- end}} {{- end}}
{{- if eq $.Interface.Type "server"}} {{- if eq $.Interface.Type "server"}}
AllowedIPs = {{ .AddressStr }}{{if ne .ExtraAllowedIPsStr ""}}, {{ .ExtraAllowedIPsStr }}{{end}} AllowedIPs = {{ CidrsToString .Interface.Addresses }}{{if ne .ExtraAllowedIPsStr ""}}, {{ .ExtraAllowedIPsStr }}{{end}}
{{- end}} {{- end}}
{{- if eq $.Interface.Type "client"}} {{- if eq $.Interface.Type "client"}}
{{- if .AllowedIPsStr}} {{- if .AllowedIPsStr.GetValue}}
AllowedIPs = {{ .AllowedIPsStr }} AllowedIPs = {{ .AllowedIPsStr.GetValue }}
{{- end}} {{- end}}
{{- end}} {{- end}}
{{- if and (ne .Endpoint "") (eq $.Interface.Type "client")}} {{- if and (ne .Endpoint.GetValue "") (eq $.Interface.Type "client")}}
Endpoint = {{ .Endpoint }} Endpoint = {{ .Endpoint.GetValue }}
{{- end}} {{- end}}
{{- if ne .PersistentKeepalive 0}} {{- if and (ne .PersistentKeepalive.GetValue 0) (eq $.Interface.Type "client")}}
PersistentKeepalive = {{ .PersistentKeepalive }} PersistentKeepalive = {{ .PersistentKeepalive.GetValue }}
{{- end}} {{- end}}
{{- end}} {{- end}}
{{end}} {{end}}

View File

@ -1,22 +1,27 @@
# AUTOGENERATED FILE - DO NOT EDIT # AUTOGENERATED FILE - DO NOT EDIT
# This file uses wg-quick format. See https://man7.org/linux/man-pages/man8/wg-quick.8.html#CONFIGURATION # This file uses wg-quick format.
# See https://man7.org/linux/man-pages/man8/wg-quick.8.html#CONFIGURATION
# Lines starting with the -WGP- tag are used by
# the WireGuard Portal configuration parser.
# -WGP- WIREGUARD PORTAL CONFIGURATION FILE, version {{ .Portal.Version }} # -WGP- WIREGUARD PORTAL CONFIGURATION FILE
# Lines starting with the -WGP- tag are used by the WireGuard Portal configuration parser. # -WGP- version {{ .Portal.Version }}
[Interface] [Interface]
# -WGP- Peer: {{.Peer.Identifier}} | Updated: {{.Peer.UpdatedAt}} | Created: {{.Peer.CreatedAt}} # -WGP- Peer: {{.Peer.Identifier}}
# -WGP- Created: {{.Peer.CreatedAt}}
# -WGP- Updated: {{.Peer.UpdatedAt}}
# -WGP- Display name: {{ .Peer.DisplayName }} # -WGP- Display name: {{ .Peer.DisplayName }}
# -WGP- PublicKey: {{ .Peer.KeyPair.PublicKey }} # -WGP- PublicKey: {{ .Peer.Interface.KeyPair.PublicKey }}
{{- if eq .Peer.Interface.Type "server"}} {{- if eq .Peer.Interface.Type "server"}}
# -WGP- Peer type: client
{{else}}
# -WGP- Peer type: server # -WGP- Peer type: server
{{else}}
# -WGP- Peer type: client
{{- end}} {{- end}}
# Core settings # Core settings
PrivateKey = {{ .Peer.KeyPair.PrivateKey }} PrivateKey = {{ .Peer.Interface.KeyPair.PrivateKey }}
Address = {{ .Peer.Interface.AddressStr }} Address = {{ CidrsToString .Peer.Interface.Addresses }}
# Misc. settings (optional) # Misc. settings (optional)
{{- if .Peer.Interface.DnsStr.GetValue}} {{- if .Peer.Interface.DnsStr.GetValue}}
@ -47,7 +52,7 @@ PostDown = {{ .Peer.Interface.PostDown.GetValue }}
{{- end}} {{- end}}
[Peer] [Peer]
PublicKey = {{ .Peer.Interface.PublicKey }} PublicKey = {{ .Peer.EndpointPublicKey.GetValue }}
Endpoint = {{ .Peer.Endpoint.GetValue }} Endpoint = {{ .Peer.Endpoint.GetValue }}
{{- if .Peer.AllowedIPsStr.GetValue}} {{- if .Peer.AllowedIPsStr.GetValue}}
AllowedIPs = {{ .Peer.AllowedIPsStr.GetValue }} AllowedIPs = {{ .Peer.AllowedIPsStr.GetValue }}
@ -55,6 +60,6 @@ AllowedIPs = {{ .Peer.AllowedIPsStr.GetValue }}
{{- if .Peer.PresharedKey}} {{- if .Peer.PresharedKey}}
PresharedKey = {{ .Peer.PresharedKey }} PresharedKey = {{ .Peer.PresharedKey }}
{{- end}} {{- end}}
{{- if ne .Peer.PersistentKeepalive.GetValue 0}} {{- if and (ne .Peer.PersistentKeepalive.GetValue 0) (eq .Peer.Interface.Type "client")}}
PersistentKeepalive = {{ .Peer.PersistentKeepalive.GetValue }} PersistentKeepalive = {{ .Peer.PersistentKeepalive.GetValue }}
{{- end}} {{- end}}

View File

@ -51,4 +51,5 @@ type StatisticsCollector interface {
type TemplateManager interface { type TemplateManager interface {
GetInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) (io.Reader, error) GetInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) (io.Reader, error)
GetPeerConfig(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error) GetPeerConfig(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error)
GetPeerConfigQrCode(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error)
} }