Browse Source

Added functionality that allows to enable

a client only until a certain date

	modified:   Dockerfile
	modified:   docker-compose.yml
	modified:   src/lib/Server.js
	modified:   src/lib/WireGuard.js
	new file:   src/services/ClientSwitcher.go
	modified:   src/www/css/app.css
	modified:   src/www/index.html
	modified:   src/www/js/api.js
	modified:   src/www/js/app.js
	modified:   src/www/js/i18n.js
pull/1200/head
Tagro 2 years ago
parent
commit
bde16189b9
  1. 25
      Dockerfile
  2. 2
      docker-compose.yml
  3. 9
      src/lib/Server.js
  4. 12
      src/lib/WireGuard.js
  5. 114
      src/services/ClientSwitcher.go
  6. 4
      src/www/css/app.css
  7. 6
      src/www/index.html
  8. 8
      src/www/js/api.js
  9. 17
      src/www/js/app.js
  10. 2
      src/www/js/i18n.js

25
Dockerfile

@ -34,12 +34,31 @@ RUN apk add --no-cache \
iptables-legacy \
wireguard-tools
# Download and instll Go
RUN wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz &&\
tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz &&\
rm go1.22.5.linux-amd64.tar.gz
# Configure Go
RUN echo 'export PATH=$PATH:/usr/local/go/bin' >> /etc/profile &&\
echo 'export GOPATH=$HOME/goproject' >> /etc/profile &&\
echo 'export PATH=$PATH:$GOPATH/bin' >> /etc/profile &&\
source /etc/profile &&\
mkdir $HOME/goproject
# Set Go Environment
ENV PATH=$PATH:/usr/local/go/bin
ENV GOPATH=/root/goproject
ENV PATH=$PATH:$GOPATH/bin
# Use iptables-legacy
RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save
# Set Environment
# Set WG Environment
ENV DEBUG=Server,WireGuard
# Run Web UI
# Start cron in the background and then start the main application
WORKDIR /app
CMD ["/usr/bin/dumb-init", "node", "server.js"]
CMD ["/usr/bin/dumb-init",\
"/bin/sh", "-c", "exec node server.js & watch -n 86400 go run ./services/ClientSwitcher.go ${WG_HOST} ${PORT} ${PASSWORD}"]

2
docker-compose.yml

@ -29,7 +29,7 @@ services:
# - UI_TRAFFIC_STATS=true
# - UI_CHART_TYPE=0 # (0 Charts disabled, 1 # Line chart, 2 # Area chart, 3 # Bar chart)
image: ghcr.io/wg-easy/wg-easy
image: ghcr.io/amarseillaise/wg-easy-amars
container_name: wg-easy
volumes:
- etc_wireguard:/etc/wireguard

9
src/lib/Server.js

@ -223,6 +223,15 @@ module.exports = class Server {
await WireGuard.disableClient({ clientId });
return { success: true };
}))
.put('/api/wireguard/client/:clientId/enableduntil', defineEventHandler(async (event) => {
const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
throw createError({ status: 403 });
}
const { date } = await readBody(event);
await WireGuard.changeEnabledUntilDate({ clientId, date });
return { success: true };
}))
.put('/api/wireguard/client/:clientId/name', defineEventHandler(async (event) => {
const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {

12
src/lib/WireGuard.js

@ -136,6 +136,9 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
id: clientId,
name: client.name,
enabled: client.enabled,
enabledUntil: client.enabledUntil !== null
? new Date(client.enabledUntil)
: null,
address: client.address,
publicKey: client.publicKey,
createdAt: new Date(client.createdAt),
@ -261,6 +264,7 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
updatedAt: new Date(),
enabled: true,
enabledUntil: null
};
config.clients[id] = client;
@ -297,6 +301,14 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
await this.saveConfig();
}
async changeEnabledUntilDate({ clientId, date }) {
const client = await this.getClient({ clientId });
const dateObj = new Date(date);
client.enabledUntil = dateObj;
await this.saveConfig();
}
async updateClientName({ clientId, name }) {
const client = await this.getClient({ clientId });

114
src/services/ClientSwitcher.go

@ -0,0 +1,114 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/cookiejar"
"os"
"strings"
"time"
)
type Client struct {
ID string `json:"id"`
Enabled bool `json:"enabled"`
EnabledUntil time.Time `json:"enabledUntil"`
}
const (
Api = "api"
Wireguard = "wireguard"
ClientApi = "client"
Session = "session"
)
func makeRequest(client *http.Client, url string, method string) *http.Response {
request, _ := http.NewRequest(method, url, nil)
response, err := client.Do(request)
if err != nil || response.StatusCode != 200 {
fmt.Println(response)
panic(response)
}
return response
}
func initClients(resp *http.Response, clients *[]Client) error {
defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(clients)
}
func switchClientsStatus(httpClient *http.Client, clients []Client) {
today := time.Now()
for _, client := range clients {
if client.EnabledUntil.Before(today) {
switchStatus(httpClient, client.ID, "disable")
} else if client.EnabledUntil.After(today) {
switchStatus(httpClient, client.ID, "enable")
}
}
}
func switchStatus(client *http.Client, clientID, status string) {
url := strings.Join([]string{getHost(), Api, Wireguard, ClientApi, clientID, status}, "/")
makeRequest(client, url, "POST")
}
func checkAuth(client *http.Client) bool {
type AuthChecker struct {
RequiresPassword bool `json:"requiresPassword"`
Authenticated bool `json:"authenticated"`
}
var auth AuthChecker
url := strings.Join([]string{getHost(), Api, Session}, "/")
response := makeRequest(client, url, "GET")
json.NewDecoder(response.Body).Decode(&auth)
result := (auth.RequiresPassword && auth.Authenticated) || (!auth.RequiresPassword && auth.Authenticated)
return result
}
func authenticate(client *http.Client) {
args := os.Args[1:]
type Password struct {
Password string `json:"password"`
}
password := Password{
Password: args[2],
}
url := strings.Join([]string{getHost(), Api, Session}, "/")
passwdJson, _ := json.Marshal(password)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(passwdJson))
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil || resp.StatusCode != 200 {
fmt.Println(resp)
panic(resp)
}
defer resp.Body.Close()
}
func getHost() string {
args := os.Args[1:]
host := args[0] + ":" + args[1]
return host
}
func main() {
getClientsUrl := strings.Join([]string{getHost(), Api, Wireguard, ClientApi}, "/")
var clients []Client
jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}
if !checkAuth(client) {
authenticate(client)
}
resp := makeRequest(client, getClientsUrl, "GET")
initClients(resp, &clients)
switchClientsStatus(client, clients)
}

4
src/www/css/app.css

@ -1686,6 +1686,10 @@ video {
font-size: 1rem;
line-height: 1.5rem;
}
.md\:text-base-small {
font-size: 0.9rem;
line-height: 1.1rem;
}
}
.dark\:border-neutral-500:where(.dark, .dark *) {

6
src/www/index.html

@ -272,6 +272,12 @@
<div class="flex items-center justify-end">
<div class="text-gray-400 dark:text-neutral-400 flex gap-1 items-center justify-between">
<!-- Enable until data -->
<div class="flex flex-col flex-grow gap-1" style="margin-right: 30px;">
<input type="date" :title="$t('enabledUntil')" v-model="client.enabledUntil" @change="changeEnabledUntilDate(client, client.enabledUntil)"
class="rounded border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 outline-none text-black dark:text-neutral-300 dark:placeholder:text-neutral-500">
</div>
<!-- Enable/Disable -->
<div @click="disableClient(client)" v-if="client.enabled === true" :title="$t('disableClient')"
class="inline-block align-middle rounded-full w-10 h-6 mr-1 bg-red-800 cursor-pointer hover:bg-red-700 transition-all">

8
src/www/js/api.js

@ -122,6 +122,14 @@ class API {
});
}
async changeEnabledUntilDate({ clientId, date }) {
return this.call({
method: 'put',
path: `/wireguard/client/${clientId}/enableduntil/`,
body: { date },
});
}
async updateClientName({ clientId, name }) {
return this.call({
method: 'put',

17
src/www/js/app.js

@ -195,6 +195,9 @@ new Vue({
this.clientsPersist[client.id].transferRxPrevious = client.transferRx;
this.clientsPersist[client.id].transferTxCurrent = client.transferTx - this.clientsPersist[client.id].transferTxPrevious;
this.clientsPersist[client.id].transferTxPrevious = client.transferTx;
this.clientsPersist[client.id].enabledUntil = client.enabledUntil !== null
? new Date(client.enabledUntil).toISOString().slice(0, 10)
: null;
if (updateCharts) {
this.clientsPersist[client.id].transferRxHistory.push(this.clientsPersist[client.id].transferRxCurrent);
@ -226,6 +229,7 @@ new Vue({
client.hoverTx = this.clientsPersist[client.id].hoverTx;
client.hoverRx = this.clientsPersist[client.id].hoverRx;
client.enabledUntil = this.clientsPersist[client.id].enabledUntil;
return client;
});
@ -289,6 +293,19 @@ new Vue({
.catch((err) => alert(err.message || err.toString()))
.finally(() => this.refresh().catch(console.error));
},
changeEnabledUntilDate(client, date){
const dateObj = new Date(date);
const currentDate = new Date();
if (dateObj < currentDate) {
alert('The date cannot be less than the current date');
return;
}
this.api.changeEnabledUntilDate({ clientId: client.id, date })
.catch((err) => alert(err.message || err.toString()))
.finally(() => this.refresh().catch(console.error));
},
updateClientName(client, name) {
this.api.updateClientName({ clientId: client.id, name })
.catch((err) => alert(err.message || err.toString()))

2
src/www/js/i18n.js

@ -22,6 +22,7 @@ const messages = { // eslint-disable-line no-unused-vars
newClient: 'New Client',
disableClient: 'Disable Client',
enableClient: 'Enable Client',
enabledUntil: 'Enabled until',
noClients: 'There are no clients yet.',
noPrivKey: 'This client has no known private key. Cannot create Configuration.',
showQR: 'Show QR Code',
@ -79,6 +80,7 @@ const messages = { // eslint-disable-line no-unused-vars
newClient: 'Создать клиента',
disableClient: 'Выключить клиента',
enableClient: 'Включить клиента',
enabledUntil: 'Включено до',
noClients: 'Пока нет клиентов.',
showQR: 'Показать QR-код',
downloadConfig: 'Скачать конфигурацию',

Loading…
Cancel
Save