mirror of
https://github.com/ngoduykhanh/wireguard-ui.git
synced 2025-06-07 00:46:58 +03:00
Merge branch 'master' into try-add-apikey
This commit is contained in:
commit
c8111e4b2c
31 changed files with 1220 additions and 220 deletions
|
@ -25,3 +25,6 @@ docker-compose*
|
||||||
db
|
db
|
||||||
assets
|
assets
|
||||||
wireguard-ui
|
wireguard-ui
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
examples
|
||||||
|
|
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
|
@ -40,20 +40,12 @@ jobs:
|
||||||
node-version: '14'
|
node-version: '14'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
# prepare assets for go rice
|
# prepare assets
|
||||||
- name: Prepare assets
|
- name: Prepare assets
|
||||||
run: |
|
run: |
|
||||||
chmod +x ./prepare_assets.sh
|
chmod +x ./prepare_assets.sh
|
||||||
./prepare_assets.sh
|
./prepare_assets.sh
|
||||||
|
|
||||||
# get go rice tool
|
|
||||||
- name: Get go rice tool
|
|
||||||
run: go get github.com/GeertJohan/go.rice/rice
|
|
||||||
|
|
||||||
# run go rice embed
|
|
||||||
- name: Run go rice embed
|
|
||||||
run: ${HOME}/go/bin/rice embed-go
|
|
||||||
|
|
||||||
# build and make the releases
|
# build and make the releases
|
||||||
- name: Build and make the releases
|
- name: Build and make the releases
|
||||||
uses: wangyoucao577/go-release-action@master
|
uses: wangyoucao577/go-release-action@master
|
||||||
|
@ -62,6 +54,7 @@ jobs:
|
||||||
goos: ${{ matrix.goos }}
|
goos: ${{ matrix.goos }}
|
||||||
goarch: ${{ matrix.goarch }}
|
goarch: ${{ matrix.goarch }}
|
||||||
goversion: "https://dl.google.com/go/go1.16.1.linux-amd64.tar.gz"
|
goversion: "https://dl.google.com/go/go1.16.1.linux-amd64.tar.gz"
|
||||||
|
pre_command: export CGO_ENABLED=0
|
||||||
binary_name: "wireguard-ui"
|
binary_name: "wireguard-ui"
|
||||||
build_flags: -v
|
build_flags: -v
|
||||||
ldflags: -X "main.appVersion=${{ env.APP_VERSION }}" -X "main.buildTime=${{ env.BUILD_TIME }}" -X main.gitCommit=${{ github.sha }} -X main.gitRef=${{ github.ref }}
|
ldflags: -X "main.appVersion=${{ env.APP_VERSION }}" -X "main.buildTime=${{ env.BUILD_TIME }}" -X main.gitCommit=${{ github.sha }} -X main.gitRef=${{ github.ref }}
|
||||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -16,8 +16,11 @@ wireguard-ui
|
||||||
vendor/
|
vendor/
|
||||||
assets/
|
assets/
|
||||||
node_modules/
|
node_modules/
|
||||||
rice-box.go
|
|
||||||
|
|
||||||
# IDEs
|
# IDEs
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
examples/docker-compose/config
|
||||||
|
examples/docker-compose/db
|
||||||
|
|
|
@ -4,6 +4,7 @@ LABEL maintainer="Khanh Ngo <k@ndk.name"
|
||||||
|
|
||||||
ARG TARGETOS=linux
|
ARG TARGETOS=linux
|
||||||
ARG TARGETARCH=amd64
|
ARG TARGETARCH=amd64
|
||||||
|
ARG COMMIT=
|
||||||
|
|
||||||
ARG BUILD_DEPENDENCIES="npm \
|
ARG BUILD_DEPENDENCIES="npm \
|
||||||
yarn"
|
yarn"
|
||||||
|
@ -42,10 +43,6 @@ RUN mkdir -p assets/plugins && \
|
||||||
/build/node_modules/jquery-tags-input/ \
|
/build/node_modules/jquery-tags-input/ \
|
||||||
assets/plugins/
|
assets/plugins/
|
||||||
|
|
||||||
# Get go modules and build tool
|
|
||||||
RUN go mod download && \
|
|
||||||
go get github.com/GeertJohan/go.rice/rice
|
|
||||||
|
|
||||||
# Add sources
|
# Add sources
|
||||||
COPY . /build
|
COPY . /build
|
||||||
|
|
||||||
|
@ -53,8 +50,7 @@ COPY . /build
|
||||||
RUN cp -r /build/custom/ assets/
|
RUN cp -r /build/custom/ assets/
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
RUN rice embed-go && \
|
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-X main.gitCommit=${COMMIT}" -a -o wg-ui .
|
||||||
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -o wg-ui .
|
|
||||||
|
|
||||||
# Release stage
|
# Release stage
|
||||||
FROM alpine:3.16
|
FROM alpine:3.16
|
||||||
|
|
34
README.md
34
README.md
|
@ -35,15 +35,6 @@ volume mount points to work with your setup. Then run it like below:
|
||||||
docker-compose up
|
docker-compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
Note:
|
|
||||||
|
|
||||||
- There is a Status page that needs docker to be able to access the network of the host in order to read the
|
|
||||||
wireguard interface stats. See the `cap_add` and `network_mode` options on the docker-compose.yaml
|
|
||||||
- Similarly, the `WGUI_MANAGE_START` and `WGUI_MANAGE_RESTART` settings need the same access, in order to restart the
|
|
||||||
wireguard interface.
|
|
||||||
- Because the `network_mode` is set to `host`, we don't need to specify the exposed ports. The app will listen on
|
|
||||||
port `5000` by default.
|
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
|
@ -53,14 +44,16 @@ Note:
|
||||||
| `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` |
|
| `WGUI_USERNAME` | The username for the login page. Used for db initialization only | `admin` |
|
||||||
| `WGUI_PASSWORD` | The password for the user on the login page. Will be hashed automatically. Used for db initialization only | `admin` |
|
| `WGUI_PASSWORD` | The password for the user on the login page. Will be hashed automatically. Used for db initialization only | `admin` |
|
||||||
| `WGUI_PASSWORD_HASH` | The password hash for the user on the login page. (alternative to `WGUI_PASSWORD`). Used for db initialization only | N/A |
|
| `WGUI_PASSWORD_HASH` | The password hash for the user on the login page. (alternative to `WGUI_PASSWORD`). Used for db initialization only | N/A |
|
||||||
|
| `WGUI_ENDPOINT_ADDRESS` | The default endpoint address used in global settings where clients should connect to | Resolved to your public ip address |
|
||||||
| `WGUI_FAVICON_FILE_PATH` | The file path used as website favicon | Embedded WireGuard logo |
|
| `WGUI_FAVICON_FILE_PATH` | The file path used as website favicon | Embedded WireGuard logo |
|
||||||
| `WGUI_ENDPOINT_ADDRESS` | The default endpoint address used in global settings | Resolved to your public ip address |
|
| `WGUI_ENDPOINT_ADDRESS` | The default endpoint address used in global settings | Resolved to your public ip address |
|
||||||
| `WGUI_DNS` | The default DNS servers (comma-separated-list) used in the global settings | `1.1.1.1` |
|
| `WGUI_DNS` | The default DNS servers (comma-separated-list) used in the global settings | `1.1.1.1` |
|
||||||
| `WGUI_MTU` | The default MTU used in global settings | `1450` |
|
| `WGUI_MTU` | The default MTU used in global settings | `1450` |
|
||||||
| `WGUI_PERSISTENT_KEEPALIVE` | The default persistent keepalive for WireGuard in global settings | `15` |
|
| `WGUI_PERSISTENT_KEEPALIVE` | The default persistent keepalive for WireGuard in global settings | `15` |
|
||||||
| `WGUI_FORWARD_MARK` | The default WireGuard forward mark | `0xca6c` |
|
| `WGUI_FIREWALL_MARK` | The default WireGuard firewall mark | `0xca6c` (51820) |
|
||||||
| `WGUI_CONFIG_FILE_PATH` | The default WireGuard config file path used in global settings | `/etc/wireguard/wg0.conf` |
|
| `WGUI_CONFIG_FILE_PATH` | The default WireGuard config file path used in global settings | `/etc/wireguard/wg0.conf` |
|
||||||
| `WG_CONF_TEMPLATE` | The custom `wg.conf` config file template. Please refer to our [default template](https://github.com/alikhanich/wireguard-ui/blob/master/templates/wg.conf) | N/A |
|
| `WGUI_LOG_LEVEL` | The default log level. Possible values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `OFF` | `INFO` |
|
||||||
|
| `WG_CONF_TEMPLATE` | The custom `wg.conf` config file template. Please refer to our [default template](https://github.com/ngoduykhanh/wireguard-ui/blob/master/templates/wg.conf) | N/A |
|
||||||
| `EMAIL_FROM_ADDRESS` | The sender email address | N/A |
|
| `EMAIL_FROM_ADDRESS` | The sender email address | N/A |
|
||||||
| `EMAIL_FROM_NAME` | The sender name | `WireGuard UI` |
|
| `EMAIL_FROM_NAME` | The sender name | `WireGuard UI` |
|
||||||
| `SENDGRID_API_KEY` | The SendGrid api key | N/A |
|
| `SENDGRID_API_KEY` | The SendGrid api key | N/A |
|
||||||
|
@ -202,7 +195,13 @@ feature work.
|
||||||
Go to the project root directory and run the following command:
|
Go to the project root directory and run the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker build -t wireguard-ui .
|
docker build --build-arg=COMMIT=$(git rev-parse --short HEAD) -t wireguard-ui .
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose build --build-arg=COMMIT=$(git rev-parse --short HEAD)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build binary file
|
### Build binary file
|
||||||
|
@ -213,18 +212,9 @@ Prepare the assets directory
|
||||||
./prepare_assets.sh
|
./prepare_assets.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you can embed resources by generating Go source code
|
Then build your executable
|
||||||
|
|
||||||
```sh
|
|
||||||
rice embed-go
|
|
||||||
go build -o wireguard-ui
|
|
||||||
```
|
|
||||||
|
|
||||||
Or, append resources to executable as zip file
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go build -o wireguard-ui
|
go build -o wireguard-ui
|
||||||
rice append --exec wireguard-ui
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
@ -58,6 +58,7 @@ function renderClientList(data) {
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<span class="info-box-text"><i class="fas fa-user"></i> ${obj.Client.name}</span>
|
<span class="info-box-text"><i class="fas fa-user"></i> ${obj.Client.name}</span>
|
||||||
|
<span class="info-box-text" style="display: none"><i class="fas fa-key"></i> ${obj.Client.public_key}</span>
|
||||||
<span class="info-box-text"><i class="fas fa-envelope"></i> ${obj.Client.email}</span>
|
<span class="info-box-text"><i class="fas fa-envelope"></i> ${obj.Client.email}</span>
|
||||||
<span class="info-box-text"><i class="fas fa-clock"></i>
|
<span class="info-box-text"><i class="fas fa-clock"></i>
|
||||||
${prettyDateTime(obj.Client.created_at)}</span>
|
${prettyDateTime(obj.Client.created_at)}</span>
|
||||||
|
@ -78,6 +79,34 @@ function renderClientList(data) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderUserList(data) {
|
||||||
|
$.each(data, function(index, obj) {
|
||||||
|
let clientStatusHtml = '>'
|
||||||
|
|
||||||
|
// render user html content
|
||||||
|
let html = `<div class="col-sm-6 col-md-6 col-lg-4" id="user_${obj.username}">
|
||||||
|
<div class="info-box">
|
||||||
|
<div class="info-box-content">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" data-target="#modal_edit_user" data-username="${obj.username}">Edit</button>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal"
|
||||||
|
data-target="#modal_remove_user" data-username="${obj.username}">Delete</button>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<span class="info-box-text"><i class="fas fa-user"></i> ${obj.username}</span>
|
||||||
|
<span class="info-box-text"><i class="fas fa-terminal"></i> ${obj.admin? 'Administrator':'Manager'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
|
||||||
|
// add the user html elements to the list
|
||||||
|
$('#users-list').append(html);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function prettyDateTime(timeStr) {
|
function prettyDateTime(timeStr) {
|
||||||
const dt = new Date(timeStr);
|
const dt = new Date(timeStr);
|
||||||
const offsetMs = dt.getTimezoneOffset() * 60 * 1000;
|
const offsetMs = dt.getTimezoneOffset() * 60 * 1000;
|
||||||
|
|
30
examples/docker-compose/README.md
Normal file
30
examples/docker-compose/README.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### Kernel Module
|
||||||
|
|
||||||
|
Depending on if the Wireguard kernel module is available on your system you have more or less choices which example to use.
|
||||||
|
|
||||||
|
You can check if the kernel modules are available via the following command:
|
||||||
|
```shell
|
||||||
|
modprobe wireguard
|
||||||
|
```
|
||||||
|
|
||||||
|
If the command exits successfully and doesn't print an error the kernel modules are available.
|
||||||
|
If it does error, you either have to install them manually (or activate if deactivated) or use an userspace implementation.
|
||||||
|
For an example of an userspace implementation, see _borigtun_.
|
||||||
|
|
||||||
|
### Credentials
|
||||||
|
|
||||||
|
Username and password for all examples is `admin` by default.
|
||||||
|
For security reasons it's highly recommended to change them before the first startup.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
- **[system](system.yml)**
|
||||||
|
|
||||||
|
If you have Wireguard already installed on your system and only want to run the UI in docker this might fit the most.
|
||||||
|
- **[linuxserver](linuxserver.yml)**
|
||||||
|
|
||||||
|
If you have the Wireguard kernel modules installed (included in the mainline kernel since version 5.6) but want it running inside of docker, this might fit the most.
|
||||||
|
- **[boringtun](boringtun.yml)**
|
||||||
|
|
||||||
|
If Wireguard kernel modules are not available, you can switch to an userspace implementation like [boringtun](https://github.com/cloudflare/boringtun).
|
43
examples/docker-compose/boringtun.yml
Normal file
43
examples/docker-compose/boringtun.yml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
boringtun:
|
||||||
|
image: ghcr.io/ntkme/boringtun:edge
|
||||||
|
command:
|
||||||
|
- wg0
|
||||||
|
container_name: boringtun
|
||||||
|
# use the network of the 'wireguard-ui' service. this enables to show active clients in the status page
|
||||||
|
network_mode: service:wireguard-ui
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
volumes:
|
||||||
|
- /dev/net/tun:/dev/net/tun
|
||||||
|
- ./config:/etc/wireguard
|
||||||
|
|
||||||
|
wireguard-ui:
|
||||||
|
image: ngoduykhanh/wireguard-ui:latest
|
||||||
|
container_name: wireguard-ui
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
environment:
|
||||||
|
- SENDGRID_API_KEY
|
||||||
|
- EMAIL_FROM_ADDRESS
|
||||||
|
- EMAIL_FROM_NAME
|
||||||
|
- SESSION_SECRET
|
||||||
|
- WGUI_USERNAME=admin
|
||||||
|
- WGUI_PASSWORD=admin
|
||||||
|
- WG_CONF_TEMPLATE
|
||||||
|
- WGUI_MANAGE_START=true
|
||||||
|
- WGUI_MANAGE_RESTART=true
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: 50m
|
||||||
|
volumes:
|
||||||
|
- ./db:/app/db
|
||||||
|
- ./config:/etc/wireguard
|
||||||
|
ports:
|
||||||
|
# port for wireguard-ui
|
||||||
|
- "5000:5000"
|
||||||
|
# port of the wireguard server. this must be set here as the `boringtun` container joins the network of this container and hasn't its own network over which it could publish the ports
|
||||||
|
- "51820:51820/udp"
|
42
examples/docker-compose/linuxserver.yml
Normal file
42
examples/docker-compose/linuxserver.yml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
wireguard:
|
||||||
|
image: linuxserver/wireguard:latest
|
||||||
|
container_name: wireguard
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
volumes:
|
||||||
|
- ./config:/config
|
||||||
|
ports:
|
||||||
|
# port for wireguard-ui. this must be set here as the `wireguard-ui` container joins the network of this container and hasn't its own network over which it could publish the ports
|
||||||
|
- "5000:5000"
|
||||||
|
# port of the wireguard server
|
||||||
|
- "51820:51820/udp"
|
||||||
|
|
||||||
|
wireguard-ui:
|
||||||
|
image: ngoduykhanh/wireguard-ui:latest
|
||||||
|
container_name: wireguard-ui
|
||||||
|
depends_on:
|
||||||
|
- wireguard
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
# use the network of the 'wireguard' service. this enables to show active clients in the status page
|
||||||
|
network_mode: service:wireguard
|
||||||
|
environment:
|
||||||
|
- SENDGRID_API_KEY
|
||||||
|
- EMAIL_FROM_ADDRESS
|
||||||
|
- EMAIL_FROM_NAME
|
||||||
|
- SESSION_SECRET
|
||||||
|
- WGUI_USERNAME=admin
|
||||||
|
- WGUI_PASSWORD=admin
|
||||||
|
- WG_CONF_TEMPLATE
|
||||||
|
- WGUI_MANAGE_START=true
|
||||||
|
- WGUI_MANAGE_RESTART=true
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: 50m
|
||||||
|
volumes:
|
||||||
|
- ./db:/app/db
|
||||||
|
- ./config:/etc/wireguard
|
27
examples/docker-compose/system.yml
Normal file
27
examples/docker-compose/system.yml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
wireguard-ui:
|
||||||
|
image: ngoduykhanh/wireguard-ui:latest
|
||||||
|
container_name: wireguard-ui
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
# required to show active clients. with this set, you don't need to expose the ui port (5000) anymore
|
||||||
|
network_mode: host
|
||||||
|
environment:
|
||||||
|
- SENDGRID_API_KEY
|
||||||
|
- EMAIL_FROM_ADDRESS
|
||||||
|
- EMAIL_FROM_NAME
|
||||||
|
- SESSION_SECRET
|
||||||
|
- WGUI_USERNAME=admin
|
||||||
|
- WGUI_PASSWORD=admin
|
||||||
|
- WG_CONF_TEMPLATE
|
||||||
|
- WGUI_MANAGE_START=false
|
||||||
|
- WGUI_MANAGE_RESTART=false
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: 50m
|
||||||
|
volumes:
|
||||||
|
- ./db:/app/db
|
||||||
|
- /etc/wireguard:/etc/wireguard
|
6
go.mod
6
go.mod
|
@ -1,9 +1,8 @@
|
||||||
module github.com/alikhanich/wireguard-ui
|
module github.com/alikhanich/wireguard-ui
|
||||||
|
|
||||||
go 1.14
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/GeertJohan/go.rice v1.0.2
|
|
||||||
github.com/glendc/go-external-ip v0.0.0-20170425150139-139229dcdddd
|
github.com/glendc/go-external-ip v0.0.0-20170425150139-139229dcdddd
|
||||||
github.com/go-playground/universal-translator v0.17.0 // indirect
|
github.com/go-playground/universal-translator v0.17.0 // indirect
|
||||||
github.com/gorilla/sessions v1.2.0
|
github.com/gorilla/sessions v1.2.0
|
||||||
|
@ -19,7 +18,8 @@ require (
|
||||||
github.com/sendgrid/sendgrid-go v3.10.0+incompatible
|
github.com/sendgrid/sendgrid-go v3.10.0+incompatible
|
||||||
github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
|
github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
|
||||||
github.com/xhit/go-simple-mail/v2 v2.10.0
|
github.com/xhit/go-simple-mail/v2 v2.10.0
|
||||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||||
|
golang.org/x/mod v0.7.0
|
||||||
//golang.zx2c4.com/wireguard v0.0.20200121 // indirect
|
//golang.zx2c4.com/wireguard v0.0.20200121 // indirect
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||||
|
|
33
go.sum
33
go.sum
|
@ -1,8 +1,4 @@
|
||||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
|
||||||
github.com/GeertJohan/go.rice v1.0.2 h1:PtRw+Tg3oa3HYwiDBZyvOJ8LdIyf6lAovJJtr7YOAYk=
|
|
||||||
github.com/GeertJohan/go.rice v1.0.2/go.mod h1:af5vUNlDNkCjOZeSGFgIJxDje9qdjsO6hshx0gTmZt4=
|
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
|
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
|
||||||
|
@ -12,8 +8,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
||||||
github.com/casbin/casbin/v2 v2.0.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
github.com/casbin/casbin/v2 v2.0.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||||
github.com/coreos/bbolt v1.3.1-coreos.6.0.20180223184059-4f5275f4ebbf/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.1-coreos.6.0.20180223184059-4f5275f4ebbf/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
|
|
||||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -52,7 +46,6 @@ github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
|
||||||
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 h1:EFT6MH3igZK/dIVqgGbTqWVvkZ7wJ5iGN03SVtvvdd8=
|
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 h1:EFT6MH3igZK/dIVqgGbTqWVvkZ7wJ5iGN03SVtvvdd8=
|
||||||
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25/go.mod h1:sWkGw/wsaHtRsT9zGQ/WyJCotGWG/Anow/9hsAcBWRw=
|
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25/go.mod h1:sWkGw/wsaHtRsT9zGQ/WyJCotGWG/Anow/9hsAcBWRw=
|
||||||
github.com/jessevdk/go-flags v0.0.0-20150816100521-1acbbaff2f34/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v0.0.0-20150816100521-1acbbaff2f34/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||||
|
@ -109,7 +102,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
|
|
||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
@ -157,6 +149,7 @@ github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy
|
||||||
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
github.com/xhit/go-simple-mail/v2 v2.10.0 h1:nib6RaJ4qVh5HD9UE9QJqnUZyWp3upv+Z6CFxaMj0V8=
|
github.com/xhit/go-simple-mail/v2 v2.10.0 h1:nib6RaJ4qVh5HD9UE9QJqnUZyWp3upv+Z6CFxaMj0V8=
|
||||||
github.com/xhit/go-simple-mail/v2 v2.10.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
|
github.com/xhit/go-simple-mail/v2 v2.10.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
@ -164,13 +157,18 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI=
|
|
||||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||||
|
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
@ -181,11 +179,13 @@ golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
|
|
||||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -215,19 +215,27 @@ golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
|
|
||||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -236,7 +244,6 @@ golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmo
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
|
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
rice "github.com/GeertJohan/go.rice"
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/labstack/echo-contrib/session"
|
"github.com/labstack/echo-contrib/session"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
@ -52,39 +52,54 @@ func LoginPage() echo.HandlerFunc {
|
||||||
// Login for signing in handler
|
// Login for signing in handler
|
||||||
func Login(db store.IStore) echo.HandlerFunc {
|
func Login(db store.IStore) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
user := new(model.User)
|
data := make(map[string]interface{})
|
||||||
c.Bind(user)
|
err := json.NewDecoder(c.Request().Body).Decode(&data)
|
||||||
|
|
||||||
dbuser, err := db.GetUser()
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"})
|
||||||
|
}
|
||||||
|
|
||||||
|
username := data["username"].(string)
|
||||||
|
password := data["password"].(string)
|
||||||
|
rememberMe := data["rememberMe"].(bool)
|
||||||
|
|
||||||
|
dbuser, err := db.GetUserByName(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot query user from DB"})
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot query user from DB"})
|
||||||
}
|
}
|
||||||
|
|
||||||
userCorrect := subtle.ConstantTimeCompare([]byte(user.Username), []byte(dbuser.Username)) == 1
|
userCorrect := subtle.ConstantTimeCompare([]byte(username), []byte(dbuser.Username)) == 1
|
||||||
|
|
||||||
var passwordCorrect bool
|
var passwordCorrect bool
|
||||||
if dbuser.PasswordHash != "" {
|
if dbuser.PasswordHash != "" {
|
||||||
match, err := util.VerifyHash(dbuser.PasswordHash, user.Password)
|
match, err := util.VerifyHash(dbuser.PasswordHash, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify password"})
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify password"})
|
||||||
}
|
}
|
||||||
passwordCorrect = match
|
passwordCorrect = match
|
||||||
} else {
|
} else {
|
||||||
passwordCorrect = subtle.ConstantTimeCompare([]byte(user.Password), []byte(dbuser.Password)) == 1
|
passwordCorrect = subtle.ConstantTimeCompare([]byte(password), []byte(dbuser.Password)) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if userCorrect && passwordCorrect {
|
if userCorrect && passwordCorrect {
|
||||||
// TODO: refresh the token
|
// TODO: refresh the token
|
||||||
|
ageMax := 0
|
||||||
|
expiration := time.Now().Add(24 * time.Hour)
|
||||||
|
if rememberMe {
|
||||||
|
ageMax = 86400
|
||||||
|
expiration.Add(144 * time.Hour)
|
||||||
|
}
|
||||||
sess, _ := session.Get("session", c)
|
sess, _ := session.Get("session", c)
|
||||||
sess.Options = &sessions.Options{
|
sess.Options = &sessions.Options{
|
||||||
Path: util.BasePath,
|
Path: util.BasePath,
|
||||||
MaxAge: 86400,
|
MaxAge: ageMax,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// set session_token
|
// set session_token
|
||||||
tokenUID := xid.New().String()
|
tokenUID := xid.New().String()
|
||||||
sess.Values["username"] = user.Username
|
sess.Values["username"] = dbuser.Username
|
||||||
|
sess.Values["admin"] = dbuser.Admin
|
||||||
sess.Values["session_token"] = tokenUID
|
sess.Values["session_token"] = tokenUID
|
||||||
sess.Save(c.Request(), c.Response())
|
sess.Save(c.Request(), c.Response())
|
||||||
|
|
||||||
|
@ -92,7 +107,7 @@ func Login(db store.IStore) echo.HandlerFunc {
|
||||||
cookie := new(http.Cookie)
|
cookie := new(http.Cookie)
|
||||||
cookie.Name = "session_token"
|
cookie.Name = "session_token"
|
||||||
cookie.Value = tokenUID
|
cookie.Value = tokenUID
|
||||||
cookie.Expires = time.Now().Add(24 * time.Hour)
|
cookie.Expires = expiration
|
||||||
c.SetCookie(cookie)
|
c.SetCookie(cookie)
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Logged in successfully"})
|
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Logged in successfully"})
|
||||||
|
@ -102,6 +117,40 @@ func Login(db store.IStore) echo.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUsers handler return a JSON list of all users
|
||||||
|
func GetUsers(db store.IStore) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
|
||||||
|
usersList, err := db.GetUsers()
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{
|
||||||
|
false, fmt.Sprintf("Cannot get user list: %v", err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, usersList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUser handler returns a JSON object of single user
|
||||||
|
func GetUser(db store.IStore) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
|
||||||
|
username := c.Param("username")
|
||||||
|
|
||||||
|
if !isAdmin(c) && (username != currentUser(c)) {
|
||||||
|
return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "Manager cannot access other user data"})
|
||||||
|
}
|
||||||
|
|
||||||
|
userData, err := db.GetUserByName(username)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "User not found"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, userData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Logout to log a user out
|
// Logout to log a user out
|
||||||
func Logout() echo.HandlerFunc {
|
func Logout() echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
|
@ -113,21 +162,23 @@ func Logout() echo.HandlerFunc {
|
||||||
// LoadProfile to load user information
|
// LoadProfile to load user information
|
||||||
func LoadProfile(db store.IStore) echo.HandlerFunc {
|
func LoadProfile(db store.IStore) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
|
|
||||||
userInfo, err := db.GetUser()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Cannot get user information: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "profile.html", map[string]interface{}{
|
return c.Render(http.StatusOK, "profile.html", map[string]interface{}{
|
||||||
"baseData": model.BaseData{Active: "profile", CurrentUser: currentUser(c)},
|
"baseData": model.BaseData{Active: "profile", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||||
"userInfo": userInfo,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateProfile to update user information
|
// UsersSettings handler
|
||||||
func UpdateProfile(db store.IStore) echo.HandlerFunc {
|
func UsersSettings(db store.IStore) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
return c.Render(http.StatusOK, "users_settings.html", map[string]interface{}{
|
||||||
|
"baseData": model.BaseData{Active: "users-settings", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUser to update user information
|
||||||
|
func UpdateUser(db store.IStore) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
data := make(map[string]interface{})
|
data := make(map[string]interface{})
|
||||||
err := json.NewDecoder(c.Request().Body).Decode(&data)
|
err := json.NewDecoder(c.Request().Body).Decode(&data)
|
||||||
|
@ -138,8 +189,18 @@ func UpdateProfile(db store.IStore) echo.HandlerFunc {
|
||||||
|
|
||||||
username := data["username"].(string)
|
username := data["username"].(string)
|
||||||
password := data["password"].(string)
|
password := data["password"].(string)
|
||||||
|
previousUsername := data["previous_username"].(string)
|
||||||
|
admin := data["admin"].(bool)
|
||||||
|
|
||||||
user, err := db.GetUser()
|
if !isAdmin(c) && (previousUsername != currentUser(c)) {
|
||||||
|
return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "Manager cannot access other user data"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isAdmin(c) {
|
||||||
|
admin = false
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := db.GetUserByName(previousUsername)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()})
|
return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()})
|
||||||
}
|
}
|
||||||
|
@ -150,6 +211,13 @@ func UpdateProfile(db store.IStore) echo.HandlerFunc {
|
||||||
user.Username = username
|
user.Username = username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if username != previousUsername {
|
||||||
|
_, err := db.GetUserByName(username)
|
||||||
|
if err == nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "This username is taken"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if password != "" {
|
if password != "" {
|
||||||
hash, err := util.HashPassword(password)
|
hash, err := util.HashPassword(password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -158,12 +226,96 @@ func UpdateProfile(db store.IStore) echo.HandlerFunc {
|
||||||
user.PasswordHash = hash
|
user.PasswordHash = hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if previousUsername != currentUser(c) {
|
||||||
|
user.Admin = admin
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.DeleteUser(previousUsername); err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()})
|
||||||
|
}
|
||||||
if err := db.SaveUser(user); err != nil {
|
if err := db.SaveUser(user); err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()})
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()})
|
||||||
}
|
}
|
||||||
log.Infof("Updated admin user information successfully")
|
log.Infof("Updated user information successfully")
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated admin user information successfully"})
|
if previousUsername == currentUser(c) {
|
||||||
|
setUser(c, user.Username, user.Admin)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated user information successfully"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser to create new user
|
||||||
|
func CreateUser(db store.IStore) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
err := json.NewDecoder(c.Request().Body).Decode(&data)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"})
|
||||||
|
}
|
||||||
|
|
||||||
|
var user model.User
|
||||||
|
username := data["username"].(string)
|
||||||
|
password := data["password"].(string)
|
||||||
|
admin := data["admin"].(bool)
|
||||||
|
|
||||||
|
if username == "" {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"})
|
||||||
|
} else {
|
||||||
|
user.Username = username
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_, err := db.GetUserByName(username)
|
||||||
|
if err == nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "This username is taken"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, err := util.HashPassword(password)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()})
|
||||||
|
}
|
||||||
|
user.PasswordHash = hash
|
||||||
|
|
||||||
|
user.Admin = admin
|
||||||
|
|
||||||
|
if err := db.SaveUser(user); err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()})
|
||||||
|
}
|
||||||
|
log.Infof("Created user successfully")
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Created user successfully"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveUser handler
|
||||||
|
func RemoveUser(db store.IStore) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
err := json.NewDecoder(c.Request().Body).Decode(&data)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"})
|
||||||
|
}
|
||||||
|
|
||||||
|
username := data["username"].(string)
|
||||||
|
|
||||||
|
if username == currentUser(c) {
|
||||||
|
return c.JSON(http.StatusForbidden, jsonHTTPResponse{false, "User cannot delete itself"})
|
||||||
|
}
|
||||||
|
// delete user from database
|
||||||
|
|
||||||
|
if err := db.DeleteUser(username); err != nil {
|
||||||
|
log.Error("Cannot delete user: ", err)
|
||||||
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot delete user from database"})
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Removed user: %s", username)
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "User removed"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +331,7 @@ func WireGuardClients(db store.IStore) echo.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "clients.html", map[string]interface{}{
|
return c.Render(http.StatusOK, "clients.html", map[string]interface{}{
|
||||||
"baseData": model.BaseData{Active: "", CurrentUser: currentUser(c)},
|
"baseData": model.BaseData{Active: "", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||||
"clientDataList": clientDataList,
|
"clientDataList": clientDataList,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -532,7 +684,7 @@ func WireGuardServer(db store.IStore) echo.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "server.html", map[string]interface{}{
|
return c.Render(http.StatusOK, "server.html", map[string]interface{}{
|
||||||
"baseData": model.BaseData{Active: "wg-server", CurrentUser: currentUser(c)},
|
"baseData": model.BaseData{Active: "wg-server", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||||
"serverInterface": server.Interface,
|
"serverInterface": server.Interface,
|
||||||
"serverKeyPair": server.KeyPair,
|
"serverKeyPair": server.KeyPair,
|
||||||
})
|
})
|
||||||
|
@ -600,7 +752,7 @@ func GlobalSettings(db store.IStore) echo.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "global_settings.html", map[string]interface{}{
|
return c.Render(http.StatusOK, "global_settings.html", map[string]interface{}{
|
||||||
"baseData": model.BaseData{Active: "global-settings", CurrentUser: currentUser(c)},
|
"baseData": model.BaseData{Active: "global-settings", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||||
"globalSettings": globalSettings,
|
"globalSettings": globalSettings,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -630,7 +782,7 @@ func Status(db store.IStore) echo.HandlerFunc {
|
||||||
wgClient, err := wgctrl.New()
|
wgClient, err := wgctrl.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{
|
return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{
|
||||||
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)},
|
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"devices": nil,
|
"devices": nil,
|
||||||
})
|
})
|
||||||
|
@ -639,7 +791,7 @@ func Status(db store.IStore) echo.HandlerFunc {
|
||||||
devices, err := wgClient.Devices()
|
devices, err := wgClient.Devices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{
|
return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{
|
||||||
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)},
|
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"devices": nil,
|
"devices": nil,
|
||||||
})
|
})
|
||||||
|
@ -651,7 +803,7 @@ func Status(db store.IStore) echo.HandlerFunc {
|
||||||
clients, err := db.GetClients(false)
|
clients, err := db.GetClients(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{
|
return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{
|
||||||
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)},
|
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"devices": nil,
|
"devices": nil,
|
||||||
})
|
})
|
||||||
|
@ -697,7 +849,7 @@ func Status(db store.IStore) echo.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "status.html", map[string]interface{}{
|
return c.Render(http.StatusOK, "status.html", map[string]interface{}{
|
||||||
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)},
|
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||||
"devices": devicesVm,
|
"devices": devicesVm,
|
||||||
"error": "",
|
"error": "",
|
||||||
})
|
})
|
||||||
|
@ -796,7 +948,7 @@ func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyServerConfig handler to write config file and restart Wireguard server
|
// ApplyServerConfig handler to write config file and restart Wireguard server
|
||||||
func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc {
|
func ApplyServerConfig(db store.IStore, tmplDir fs.FS) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
|
|
||||||
server, err := db.GetServer()
|
server, err := db.GetServer()
|
||||||
|
@ -811,6 +963,12 @@ func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc {
|
||||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get client config"})
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get client config"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
users, err := db.GetUsers()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Cannot get users config: ", err)
|
||||||
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get users config"})
|
||||||
|
}
|
||||||
|
|
||||||
settings, err := db.GetGlobalSettings()
|
settings, err := db.GetGlobalSettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Cannot get global settings: ", err)
|
log.Error("Cannot get global settings: ", err)
|
||||||
|
@ -818,7 +976,7 @@ func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write config file
|
// Write config file
|
||||||
err = util.WriteWireGuardServerConfig(tmplBox, server, clients, settings)
|
err = util.WriteWireGuardServerConfig(tmplDir, server, clients, users, settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Cannot apply server config: ", err)
|
log.Error("Cannot apply server config: ", err)
|
||||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{
|
||||||
|
@ -826,16 +984,35 @@ func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = util.UpdateHashes(db)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Cannot update hashes: ", err)
|
||||||
|
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{
|
||||||
|
false, fmt.Sprintf("Cannot update hashes: %v", err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Applied server config successfully"})
|
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Applied server config successfully"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// GetHashesChanges handler returns if database hashes have changed
|
||||||
|
func GetHashesChanges(db store.IStore) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
if util.HashesChanged(db) {
|
||||||
|
return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Hashes changed"})
|
||||||
|
} else {
|
||||||
|
return c.JSON(http.StatusOK, jsonHTTPResponse{false, "Hashes not changed"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AboutPage handler
|
// AboutPage handler
|
||||||
func AboutPage() echo.HandlerFunc {
|
func AboutPage() echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
return c.Render(http.StatusOK, "about.html", map[string]interface{}{
|
return c.Render(http.StatusOK, "about.html", map[string]interface{}{
|
||||||
"baseData": model.BaseData{Active: "about", CurrentUser: currentUser(c)},
|
"baseData": model.BaseData{Active: "about", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ func GetWakeOnLanHosts(db store.IStore) echo.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Render(http.StatusOK, "wake_on_lan_hosts.html", map[string]interface{}{
|
err = c.Render(http.StatusOK, "wake_on_lan_hosts.html", map[string]interface{}{
|
||||||
"baseData": model.BaseData{Active: "wake_on_lan_hosts", CurrentUser: currentUser(c)},
|
"baseData": model.BaseData{Active: "wake_on_lan_hosts", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||||
"hosts": hosts,
|
"hosts": hosts,
|
||||||
"error": "",
|
"error": "",
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,15 +14,24 @@ func ValidSession(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
if !isValidSession(c) {
|
if !isValidSession(c) {
|
||||||
nextURL := c.Request().URL
|
nextURL := c.Request().URL
|
||||||
if nextURL != nil && c.Request().Method == http.MethodGet {
|
if nextURL != nil && c.Request().Method == http.MethodGet {
|
||||||
return c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf(util.BasePath + "/login?next=%s", c.Request().URL))
|
return c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf(util.BasePath+"/login?next=%s", c.Request().URL))
|
||||||
} else {
|
} else {
|
||||||
return c.Redirect(http.StatusTemporaryRedirect, util.BasePath + "/login")
|
return c.Redirect(http.StatusTemporaryRedirect, util.BasePath+"/login")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NeedsAdmin(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
if !isAdmin(c) {
|
||||||
|
return c.Redirect(http.StatusTemporaryRedirect, util.BasePath+"/")
|
||||||
|
}
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func isValidSession(c echo.Context) bool {
|
func isValidSession(c echo.Context) bool {
|
||||||
if util.DisableLogin {
|
if util.DisableLogin {
|
||||||
return true
|
return true
|
||||||
|
@ -53,10 +62,29 @@ func currentUser(c echo.Context) string {
|
||||||
return username
|
return username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isAdmin to get user type: admin or manager
|
||||||
|
func isAdmin(c echo.Context) bool {
|
||||||
|
if util.DisableLogin {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
sess, _ := session.Get("session", c)
|
||||||
|
admin := fmt.Sprintf("%t", sess.Values["admin"])
|
||||||
|
return admin == "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUser(c echo.Context, username string, admin bool) {
|
||||||
|
sess, _ := session.Get("session", c)
|
||||||
|
sess.Values["username"] = username
|
||||||
|
sess.Values["admin"] = admin
|
||||||
|
sess.Save(c.Request(), c.Response())
|
||||||
|
}
|
||||||
|
|
||||||
// clearSession to remove current session
|
// clearSession to remove current session
|
||||||
func clearSession(c echo.Context) {
|
func clearSession(c echo.Context) {
|
||||||
sess, _ := session.Get("session", c)
|
sess, _ := session.Get("session", c)
|
||||||
sess.Values["username"] = ""
|
sess.Values["username"] = ""
|
||||||
|
sess.Values["admin"] = false
|
||||||
sess.Values["session_token"] = ""
|
sess.Values["session_token"] = ""
|
||||||
sess.Save(c.Request(), c.Response())
|
sess.Save(c.Request(), c.Response())
|
||||||
}
|
}
|
||||||
|
|
63
main.go
63
main.go
|
@ -1,15 +1,16 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/gommon/log"
|
"github.com/labstack/gommon/log"
|
||||||
"github.com/alikhanich/wireguard-ui/store"
|
"github.com/ngoduykhanh/wireguard-ui/store"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
rice "github.com/GeertJohan/go.rice"
|
rice "github.com/GeertJohan/go.rice"
|
||||||
"github.com/alikhanich/wireguard-ui/emailer"
|
"github.com/alikhanich/wireguard-ui/emailer"
|
||||||
"github.com/alikhanich/wireguard-ui/handler"
|
"github.com/alikhanich/wireguard-ui/handler"
|
||||||
|
@ -52,6 +53,16 @@ const (
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// embed the "templates" directory
|
||||||
|
//
|
||||||
|
//go:embed templates/*
|
||||||
|
var embeddedTemplates embed.FS
|
||||||
|
|
||||||
|
// embed the "assets" directory
|
||||||
|
//
|
||||||
|
//go:embed assets/*
|
||||||
|
var embeddedAssets embed.FS
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
||||||
// command-line flags and env variables
|
// command-line flags and env variables
|
||||||
|
@ -119,19 +130,18 @@ func main() {
|
||||||
// set app extra data
|
// set app extra data
|
||||||
extraData := make(map[string]string)
|
extraData := make(map[string]string)
|
||||||
extraData["appVersion"] = appVersion
|
extraData["appVersion"] = appVersion
|
||||||
|
extraData["gitCommit"] = gitCommit
|
||||||
extraData["basePath"] = util.BasePath
|
extraData["basePath"] = util.BasePath
|
||||||
|
|
||||||
// create rice box for embedded template
|
// strip the "templates/" prefix from the embedded directory so files can be read by their direct name (e.g.
|
||||||
tmplBox := rice.MustFindBox("templates")
|
// "base.html" instead of "templates/base.html")
|
||||||
|
tmplDir, _ := fs.Sub(fs.FS(embeddedTemplates), "templates")
|
||||||
// rice file server for assets. "assets" is the folder where the files come from.
|
|
||||||
assetHandler := http.FileServer(rice.MustFindBox("assets").HTTPBox())
|
|
||||||
|
|
||||||
// create the wireguard config on start, if it doesn't exist
|
// create the wireguard config on start, if it doesn't exist
|
||||||
initServerConfig(db, tmplBox)
|
initServerConfig(db, tmplDir)
|
||||||
|
|
||||||
// register routes
|
// register routes
|
||||||
app := router.New(tmplBox, extraData, util.SessionSecret)
|
app := router.New(tmplDir, extraData, util.SessionSecret)
|
||||||
|
|
||||||
app.GET(util.BasePath, handler.WireGuardClients(db), handler.ValidSession)
|
app.GET(util.BasePath, handler.WireGuardClients(db), handler.ValidSession)
|
||||||
|
|
||||||
|
@ -140,7 +150,12 @@ func main() {
|
||||||
app.POST(util.BasePath+"/login", handler.Login(db))
|
app.POST(util.BasePath+"/login", handler.Login(db))
|
||||||
app.GET(util.BasePath+"/logout", handler.Logout(), handler.ValidSession)
|
app.GET(util.BasePath+"/logout", handler.Logout(), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/profile", handler.LoadProfile(db), handler.ValidSession)
|
app.GET(util.BasePath+"/profile", handler.LoadProfile(db), handler.ValidSession)
|
||||||
app.POST(util.BasePath+"/profile", handler.UpdateProfile(db), handler.ValidSession)
|
app.GET(util.BasePath+"/users-settings", handler.UsersSettings(db), handler.ValidSession, handler.NeedsAdmin)
|
||||||
|
app.POST(util.BasePath+"/update-user", handler.UpdateUser(db), handler.ValidSession)
|
||||||
|
app.POST(util.BasePath+"/create-user", handler.CreateUser(db), handler.ValidSession, handler.NeedsAdmin)
|
||||||
|
app.POST(util.BasePath+"/remove-user", handler.RemoveUser(db), handler.ValidSession, handler.NeedsAdmin)
|
||||||
|
app.GET(util.BasePath+"/getusers", handler.GetUsers(db), handler.ValidSession, handler.NeedsAdmin)
|
||||||
|
app.GET(util.BasePath+"/api/user/:username", handler.GetUser(db), handler.ValidSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sendmail emailer.Emailer
|
var sendmail emailer.Emailer
|
||||||
|
@ -150,6 +165,7 @@ func main() {
|
||||||
sendmail = emailer.NewSmtpMail(util.SmtpHostname, util.SmtpPort, util.SmtpUsername, util.SmtpPassword, util.SmtpNoTLSCheck, util.SmtpAuthType, util.EmailFromName, util.EmailFrom, util.SmtpEncryption)
|
sendmail = emailer.NewSmtpMail(util.SmtpHostname, util.SmtpPort, util.SmtpUsername, util.SmtpPassword, util.SmtpNoTLSCheck, util.SmtpAuthType, util.EmailFromName, util.EmailFrom, util.SmtpEncryption)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.GET(util.BasePath+"/test-hash", handler.GetHashesChanges(db), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/about", handler.AboutPage())
|
app.GET(util.BasePath+"/about", handler.AboutPage())
|
||||||
app.GET(util.BasePath+"/_health", handler.Health())
|
app.GET(util.BasePath+"/_health", handler.Health())
|
||||||
app.GET(util.BasePath+"/favicon", handler.Favicon())
|
app.GET(util.BasePath+"/favicon", handler.Favicon())
|
||||||
|
@ -159,29 +175,33 @@ func main() {
|
||||||
app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson)
|
app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson)
|
||||||
app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson)
|
app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson)
|
||||||
app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession)
|
app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/wg-server", handler.WireGuardServer(db), handler.ValidSession)
|
app.GET(util.BasePath+"/wg-server", handler.WireGuardServer(db), handler.ValidSession, handler.NeedsAdmin)
|
||||||
app.POST(util.BasePath+"/wg-server/interfaces", handler.WireGuardServerInterfaces(db), handler.ValidSession, handler.ContentTypeJson)
|
app.POST(util.BasePath+"/wg-server/interfaces", handler.WireGuardServerInterfaces(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
||||||
app.POST(util.BasePath+"/wg-server/keypair", handler.WireGuardServerKeyPair(db), handler.ValidSession, handler.ContentTypeJson)
|
app.POST(util.BasePath+"/wg-server/keypair", handler.WireGuardServerKeyPair(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
||||||
app.GET(util.BasePath+"/global-settings", handler.GlobalSettings(db), handler.ValidSession)
|
app.GET(util.BasePath+"/global-settings", handler.GlobalSettings(db), handler.ValidSession, handler.NeedsAdmin)
|
||||||
app.POST(util.BasePath+"/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession, handler.ContentTypeJson)
|
app.POST(util.BasePath+"/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)
|
||||||
app.GET(util.BasePath+"/status", handler.Status(db), handler.ValidSession)
|
app.GET(util.BasePath+"/status", handler.Status(db), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession)
|
app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession)
|
app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/api/machine-ips", handler.MachineIPAddresses(), handler.ValidSession)
|
app.GET(util.BasePath+"/api/machine-ips", handler.MachineIPAddresses(), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/api/suggest-client-ips", handler.SuggestIPAllocation(db), handler.ValidSession)
|
app.GET(util.BasePath+"/api/suggest-client-ips", handler.SuggestIPAllocation(db), handler.ValidSession)
|
||||||
app.POST(util.BasePath+"/api/apply-wg-config", handler.ApplyServerConfig(db, tmplBox), handler.ValidSession, handler.ContentTypeJson)
|
app.POST(util.BasePath+"/api/apply-wg-config", handler.ApplyServerConfig(db, tmplDir), handler.ValidSession, handler.ContentTypeJson)
|
||||||
app.GET(util.BasePath+"/wake_on_lan_hosts", handler.GetWakeOnLanHosts(db), handler.ValidSession)
|
app.GET(util.BasePath+"/wake_on_lan_hosts", handler.GetWakeOnLanHosts(db), handler.ValidSession)
|
||||||
app.POST(util.BasePath+"/wake_on_lan_host", handler.SaveWakeOnLanHost(db), handler.ValidSession, handler.ContentTypeJson)
|
app.POST(util.BasePath+"/wake_on_lan_host", handler.SaveWakeOnLanHost(db), handler.ValidSession, handler.ContentTypeJson)
|
||||||
app.DELETE(util.BasePath+"/wake_on_lan_host/:mac_address", handler.DeleteWakeOnHost(db), handler.ValidSession, handler.ContentTypeJson)
|
app.DELETE(util.BasePath+"/wake_on_lan_host/:mac_address", handler.DeleteWakeOnHost(db), handler.ValidSession, handler.ContentTypeJson)
|
||||||
app.PUT(util.BasePath+"/wake_on_lan_host/:mac_address", handler.WakeOnHost(db), handler.ValidSession, handler.ContentTypeJson)
|
app.PUT(util.BasePath+"/wake_on_lan_host/:mac_address", handler.WakeOnHost(db), handler.ValidSession, handler.ContentTypeJson)
|
||||||
|
|
||||||
// servers other static files
|
// strip the "assets/" prefix from the embedded directory so files can be called directly without the "assets/"
|
||||||
|
// prefix
|
||||||
|
assetsDir, _ := fs.Sub(fs.FS(embeddedAssets), "assets")
|
||||||
|
assetHandler := http.FileServer(http.FS(assetsDir))
|
||||||
|
// serves other static files
|
||||||
app.GET(util.BasePath+"/static/*", echo.WrapHandler(http.StripPrefix(util.BasePath+"/static/", assetHandler)))
|
app.GET(util.BasePath+"/static/*", echo.WrapHandler(http.StripPrefix(util.BasePath+"/static/", assetHandler)))
|
||||||
|
|
||||||
app.Logger.Fatal(app.Start(util.BindAddress))
|
app.Logger.Fatal(app.Start(util.BindAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
func initServerConfig(db store.IStore, tmplBox *rice.Box) {
|
func initServerConfig(db store.IStore, tmplDir fs.FS) {
|
||||||
settings, err := db.GetGlobalSettings()
|
settings, err := db.GetGlobalSettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Cannot get global settings: ", err)
|
log.Fatalf("Cannot get global settings: ", err)
|
||||||
|
@ -202,8 +222,13 @@ func initServerConfig(db store.IStore, tmplBox *rice.Box) {
|
||||||
log.Fatalf("Cannot get client config: ", err)
|
log.Fatalf("Cannot get client config: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
users, err := db.GetUsers()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Cannot get user config: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
// write config file
|
// write config file
|
||||||
err = util.WriteWireGuardServerConfig(tmplBox, server, clients, settings)
|
err = util.WriteWireGuardServerConfig(tmplDir, server, clients, users, settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Cannot create server config: ", err)
|
log.Fatalf("Cannot create server config: ", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,4 +10,11 @@ type Interface struct {
|
||||||
type BaseData struct {
|
type BaseData struct {
|
||||||
Active string
|
Active string
|
||||||
CurrentUser string
|
CurrentUser string
|
||||||
|
Admin bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientServerHashes struct, to save hashes to detect changes
|
||||||
|
type ClientServerHashes struct {
|
||||||
|
Client string `json:"client"`
|
||||||
|
Server string `json:"server"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ type GlobalSetting struct {
|
||||||
DNSServers []string `json:"dns_servers"`
|
DNSServers []string `json:"dns_servers"`
|
||||||
MTU int `json:"mtu,string"`
|
MTU int `json:"mtu,string"`
|
||||||
PersistentKeepalive int `json:"persistent_keepalive,string"`
|
PersistentKeepalive int `json:"persistent_keepalive,string"`
|
||||||
ForwardMark string `json:"forward_mark"`
|
FirewallMark string `json:"firewall_mark"`
|
||||||
ConfigFilePath string `json:"config_file_path"`
|
ConfigFilePath string `json:"config_file_path"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,5 @@ type User struct {
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
// PasswordHash takes precedence over Password.
|
// PasswordHash takes precedence over Password.
|
||||||
PasswordHash string `json:"password_hash"`
|
PasswordHash string `json:"password_hash"`
|
||||||
|
Admin bool `json:"admin"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,11 @@ package router
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
rice "github.com/GeertJohan/go.rice"
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/labstack/echo-contrib/session"
|
"github.com/labstack/echo-contrib/session"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
@ -66,54 +66,59 @@ func apiKeyMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// New function
|
// New function
|
||||||
func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Echo {
|
func New(tmplDir fs.FS, extraData map[string]string, secret []byte) *echo.Echo {
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
|
|
||||||
store := sessions.NewCookieStore(secret)
|
store := sessions.NewCookieStore(secret)
|
||||||
e.Use(session.Middleware(store))
|
e.Use(session.Middleware(store))
|
||||||
e.Use(apiKeyMiddleware)
|
e.Use(apiKeyMiddleware)
|
||||||
// read html template file to string
|
// read html template file to string
|
||||||
tmplBaseString, err := tmplBox.String("base.html")
|
tmplBaseString, err := util.StringFromEmbedFile(tmplDir, "base.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplLoginString, err := tmplBox.String("login.html")
|
tmplLoginString, err := util.StringFromEmbedFile(tmplDir, "login.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplProfileString, err := tmplBox.String("profile.html")
|
tmplProfileString, err := util.StringFromEmbedFile(tmplDir, "profile.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplClientsString, err := tmplBox.String("clients.html")
|
tmplClientsString, err := util.StringFromEmbedFile(tmplDir, "clients.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplServerString, err := tmplBox.String("server.html")
|
tmplServerString, err := util.StringFromEmbedFile(tmplDir, "server.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplGlobalSettingsString, err := tmplBox.String("global_settings.html")
|
tmplGlobalSettingsString, err := util.StringFromEmbedFile(tmplDir, "global_settings.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplStatusString, err := tmplBox.String("status.html")
|
tmplUsersSettingsString, err := util.StringFromEmbedFile(tmplDir, "users_settings.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplWakeOnLanHostsString, err := tmplBox.String("wake_on_lan_hosts.html")
|
tmplStatusString, err := util.StringFromEmbedFile(tmplDir, "status.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
aboutPageString, err := tmplBox.String("about.html")
|
tmplWakeOnLanHostsString, err := util.StringFromEmbedFile(tmplDir, "wake_on_lan_hosts.html")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
aboutPageString, err := util.StringFromEmbedFile(tmplDir, "about.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -128,14 +133,33 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec
|
||||||
templates["clients.html"] = template.Must(template.New("clients").Funcs(funcs).Parse(tmplBaseString + tmplClientsString))
|
templates["clients.html"] = template.Must(template.New("clients").Funcs(funcs).Parse(tmplBaseString + tmplClientsString))
|
||||||
templates["server.html"] = template.Must(template.New("server").Funcs(funcs).Parse(tmplBaseString + tmplServerString))
|
templates["server.html"] = template.Must(template.New("server").Funcs(funcs).Parse(tmplBaseString + tmplServerString))
|
||||||
templates["global_settings.html"] = template.Must(template.New("global_settings").Funcs(funcs).Parse(tmplBaseString + tmplGlobalSettingsString))
|
templates["global_settings.html"] = template.Must(template.New("global_settings").Funcs(funcs).Parse(tmplBaseString + tmplGlobalSettingsString))
|
||||||
|
templates["users_settings.html"] = template.Must(template.New("users_settings").Funcs(funcs).Parse(tmplBaseString + tmplUsersSettingsString))
|
||||||
templates["status.html"] = template.Must(template.New("status").Funcs(funcs).Parse(tmplBaseString + tmplStatusString))
|
templates["status.html"] = template.Must(template.New("status").Funcs(funcs).Parse(tmplBaseString + tmplStatusString))
|
||||||
templates["wake_on_lan_hosts.html"] = template.Must(template.New("wake_on_lan_hosts").Funcs(funcs).Parse(tmplBaseString + tmplWakeOnLanHostsString))
|
templates["wake_on_lan_hosts.html"] = template.Must(template.New("wake_on_lan_hosts").Funcs(funcs).Parse(tmplBaseString + tmplWakeOnLanHostsString))
|
||||||
templates["about.html"] = template.Must(template.New("about").Funcs(funcs).Parse(tmplBaseString + aboutPageString))
|
templates["about.html"] = template.Must(template.New("about").Funcs(funcs).Parse(tmplBaseString + aboutPageString))
|
||||||
|
|
||||||
e.Logger.SetLevel(log.DEBUG)
|
lvl, err := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
logConfig := middleware.DefaultLoggerConfig
|
||||||
|
logConfig.Skipper = func(c echo.Context) bool {
|
||||||
|
resp := c.Response()
|
||||||
|
if resp.Status >= 500 && lvl > log.ERROR { // do not log if response is 5XX but log level is higher than ERROR
|
||||||
|
return true
|
||||||
|
} else if resp.Status >= 400 && lvl > log.WARN { // do not log if response is 4XX but log level is higher than WARN
|
||||||
|
return true
|
||||||
|
} else if lvl > log.DEBUG { // do not log if log level is higher than DEBUG
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Logger.SetLevel(lvl)
|
||||||
e.Pre(middleware.RemoveTrailingSlash())
|
e.Pre(middleware.RemoveTrailingSlash())
|
||||||
e.Use(middleware.Logger())
|
e.Use(middleware.LoggerWithConfig(logConfig))
|
||||||
e.HideBanner = true
|
e.HideBanner = true
|
||||||
|
e.HidePort = lvl > log.INFO // hide the port output if the log level is higher than INFO
|
||||||
e.Validator = NewValidator()
|
e.Validator = NewValidator()
|
||||||
e.Renderer = &TemplateRegistry{
|
e.Renderer = &TemplateRegistry{
|
||||||
templates: templates,
|
templates: templates,
|
||||||
|
|
|
@ -42,7 +42,9 @@ func (o *JsonDB) Init() error {
|
||||||
var serverInterfacePath string = path.Join(serverPath, "interfaces.json")
|
var serverInterfacePath string = path.Join(serverPath, "interfaces.json")
|
||||||
var serverKeyPairPath string = path.Join(serverPath, "keypair.json")
|
var serverKeyPairPath string = path.Join(serverPath, "keypair.json")
|
||||||
var globalSettingPath string = path.Join(serverPath, "global_settings.json")
|
var globalSettingPath string = path.Join(serverPath, "global_settings.json")
|
||||||
|
var hashesPath string = path.Join(serverPath, "hashes.json")
|
||||||
var userPath string = path.Join(serverPath, "users.json")
|
var userPath string = path.Join(serverPath, "users.json")
|
||||||
|
|
||||||
// create directories if they do not exist
|
// create directories if they do not exist
|
||||||
if _, err := os.Stat(clientPath); os.IsNotExist(err) {
|
if _, err := os.Stat(clientPath); os.IsNotExist(err) {
|
||||||
os.MkdirAll(clientPath, os.ModePerm)
|
os.MkdirAll(clientPath, os.ModePerm)
|
||||||
|
@ -53,6 +55,9 @@ func (o *JsonDB) Init() error {
|
||||||
if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) {
|
if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) {
|
||||||
os.MkdirAll(wakeOnLanHostsPath, os.ModePerm)
|
os.MkdirAll(wakeOnLanHostsPath, os.ModePerm)
|
||||||
}
|
}
|
||||||
|
if _, err := os.Stat(userPath); os.IsNotExist(err) {
|
||||||
|
os.MkdirAll(userPath, os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
// server's interface
|
// server's interface
|
||||||
if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) {
|
if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) {
|
||||||
|
@ -96,16 +101,26 @@ func (o *JsonDB) Init() error {
|
||||||
globalSetting.DNSServers = util.LookupEnvOrStrings(util.DNSEnvVar, []string{util.DefaultDNS})
|
globalSetting.DNSServers = util.LookupEnvOrStrings(util.DNSEnvVar, []string{util.DefaultDNS})
|
||||||
globalSetting.MTU = util.LookupEnvOrInt(util.MTUEnvVar, util.DefaultMTU)
|
globalSetting.MTU = util.LookupEnvOrInt(util.MTUEnvVar, util.DefaultMTU)
|
||||||
globalSetting.PersistentKeepalive = util.LookupEnvOrInt(util.PersistentKeepaliveEnvVar, util.DefaultPersistentKeepalive)
|
globalSetting.PersistentKeepalive = util.LookupEnvOrInt(util.PersistentKeepaliveEnvVar, util.DefaultPersistentKeepalive)
|
||||||
globalSetting.ForwardMark = util.LookupEnvOrString(util.ForwardMarkEnvVar, util.DefaultForwardMark)
|
globalSetting.FirewallMark = util.LookupEnvOrString(util.FirewallMarkEnvVar, util.DefaultFirewallMark)
|
||||||
globalSetting.ConfigFilePath = util.LookupEnvOrString(util.ConfigFilePathEnvVar, util.DefaultConfigFilePath)
|
globalSetting.ConfigFilePath = util.LookupEnvOrString(util.ConfigFilePathEnvVar, util.DefaultConfigFilePath)
|
||||||
globalSetting.UpdatedAt = time.Now().UTC()
|
globalSetting.UpdatedAt = time.Now().UTC()
|
||||||
o.conn.Write("server", "global_settings", globalSetting)
|
o.conn.Write("server", "global_settings", globalSetting)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hashes
|
||||||
|
if _, err := os.Stat(hashesPath); os.IsNotExist(err) {
|
||||||
|
clientServerHashes := new(model.ClientServerHashes)
|
||||||
|
clientServerHashes.Client = "none"
|
||||||
|
clientServerHashes.Server = "none"
|
||||||
|
o.conn.Write("server", "hashes", clientServerHashes)
|
||||||
|
}
|
||||||
|
|
||||||
// user info
|
// user info
|
||||||
if _, err := os.Stat(userPath); os.IsNotExist(err) {
|
results, err := o.conn.ReadAll("users")
|
||||||
|
if err != nil || len(results) < 1 {
|
||||||
user := new(model.User)
|
user := new(model.User)
|
||||||
user.Username = util.LookupEnvOrString(util.UsernameEnvVar, util.DefaultUsername)
|
user.Username = util.LookupEnvOrString(util.UsernameEnvVar, util.DefaultUsername)
|
||||||
|
user.Admin = util.DefaultIsAdmin
|
||||||
user.PasswordHash = util.LookupEnvOrString(util.PasswordHashEnvVar, "")
|
user.PasswordHash = util.LookupEnvOrString(util.PasswordHashEnvVar, "")
|
||||||
if user.PasswordHash == "" {
|
if user.PasswordHash == "" {
|
||||||
plaintext := util.LookupEnvOrString(util.PasswordEnvVar, util.DefaultPassword)
|
plaintext := util.LookupEnvOrString(util.PasswordEnvVar, util.DefaultPassword)
|
||||||
|
@ -115,7 +130,7 @@ func (o *JsonDB) Init() error {
|
||||||
}
|
}
|
||||||
user.PasswordHash = hash
|
user.PasswordHash = hash
|
||||||
}
|
}
|
||||||
o.conn.Write("server", "users", user)
|
o.conn.Write("users", user.Username, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -127,9 +142,44 @@ func (o *JsonDB) GetUser() (model.User, error) {
|
||||||
return user, o.conn.Read("server", "users", &user)
|
return user, o.conn.Read("server", "users", &user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveUser func to user info to the database
|
// GetUsers func to get all users from the database
|
||||||
|
func (o *JsonDB) GetUsers() ([]model.User, error) {
|
||||||
|
var users []model.User
|
||||||
|
results, err := o.conn.ReadAll("users")
|
||||||
|
if err != nil {
|
||||||
|
return users, err
|
||||||
|
}
|
||||||
|
for _, i := range results {
|
||||||
|
user := model.User{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(i), &user); err != nil {
|
||||||
|
return users, fmt.Errorf("cannot decode user json structure: %v", err)
|
||||||
|
}
|
||||||
|
users = append(users, user)
|
||||||
|
|
||||||
|
}
|
||||||
|
return users, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByName func to get single user from the database
|
||||||
|
func (o *JsonDB) GetUserByName(username string) (model.User, error) {
|
||||||
|
user := model.User{}
|
||||||
|
|
||||||
|
if err := o.conn.Read("users", username, &user); err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveUser func to save user in the database
|
||||||
func (o *JsonDB) SaveUser(user model.User) error {
|
func (o *JsonDB) SaveUser(user model.User) error {
|
||||||
return o.conn.Write("server", "users", user)
|
return o.conn.Write("users", user.Username, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser func to remove user from the database
|
||||||
|
func (o *JsonDB) DeleteUser(username string) error {
|
||||||
|
return o.conn.Delete("users", username)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGlobalSettings func to query global settings from the database
|
// GetGlobalSettings func to query global settings from the database
|
||||||
|
@ -213,15 +263,12 @@ func (o *JsonDB) GetClientByID(clientID string, qrCodeSettings model.QRCodeSetti
|
||||||
server, _ := o.GetServer()
|
server, _ := o.GetServer()
|
||||||
globalSettings, _ := o.GetGlobalSettings()
|
globalSettings, _ := o.GetGlobalSettings()
|
||||||
client := client
|
client := client
|
||||||
if !qrCodeSettings.IncludeDNS{
|
if !qrCodeSettings.IncludeDNS {
|
||||||
globalSettings.DNSServers = []string{}
|
globalSettings.DNSServers = []string{}
|
||||||
}
|
}
|
||||||
if !qrCodeSettings.IncludeMTU {
|
if !qrCodeSettings.IncludeMTU {
|
||||||
globalSettings.MTU = 0
|
globalSettings.MTU = 0
|
||||||
}
|
}
|
||||||
if !qrCodeSettings.IncludeFwMark {
|
|
||||||
globalSettings.ForwardMark = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
png, err := qrcode.Encode(util.BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256)
|
png, err := qrcode.Encode(util.BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -255,3 +302,16 @@ func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error {
|
||||||
func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error {
|
func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error {
|
||||||
return o.conn.Write("server", "global_settings", globalSettings)
|
return o.conn.Write("server", "global_settings", globalSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *JsonDB) GetPath() string {
|
||||||
|
return o.dbPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *JsonDB) GetHashes() (model.ClientServerHashes, error) {
|
||||||
|
hashes := model.ClientServerHashes{}
|
||||||
|
return hashes, o.conn.Read("server", "hashes", &hashes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *JsonDB) SaveHashes(hashes model.ClientServerHashes) error {
|
||||||
|
return o.conn.Write("server", "hashes", hashes)
|
||||||
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@ import (
|
||||||
|
|
||||||
type IStore interface {
|
type IStore interface {
|
||||||
Init() error
|
Init() error
|
||||||
GetUser() (model.User, error)
|
GetUsers() ([]model.User, error)
|
||||||
|
GetUserByName(username string) (model.User, error)
|
||||||
SaveUser(user model.User) error
|
SaveUser(user model.User) error
|
||||||
|
DeleteUser(username string) error
|
||||||
GetGlobalSettings() (model.GlobalSetting, error)
|
GetGlobalSettings() (model.GlobalSetting, error)
|
||||||
GetServer() (model.Server, error)
|
GetServer() (model.Server, error)
|
||||||
GetClients(hasQRCode bool) ([]model.ClientData, error)
|
GetClients(hasQRCode bool) ([]model.ClientData, error)
|
||||||
|
@ -22,4 +24,7 @@ type IStore interface {
|
||||||
DeleteWakeOnHostLanHost(macAddress string) error
|
DeleteWakeOnHostLanHost(macAddress string) error
|
||||||
SaveWakeOnLanHost(host model.WakeOnLanHost) error
|
SaveWakeOnLanHost(host model.WakeOnLanHost) error
|
||||||
DeleteWakeOnHost(host model.WakeOnLanHost) error
|
DeleteWakeOnHost(host model.WakeOnLanHost) error
|
||||||
|
GetPath() string
|
||||||
|
SaveHashes(hashes model.ClientServerHashes) error
|
||||||
|
GetHashes() (model.ClientServerHashes, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,12 @@ About
|
||||||
<label for="version" class="control-label">Current version</label>
|
<label for="version" class="control-label">Current version</label>
|
||||||
<input type="text" class="form-control" id="version" value="{{ .appVersion }}" readonly>
|
<input type="text" class="form-control" id="version" value="{{ .appVersion }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
|
{{ if .gitCommit }}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="version" class="control-label">git commit hash</label>
|
||||||
|
<input type="text" class="form-control" id="version" value="{{ .gitCommit }}" readonly>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="currentReleaseDate" class="control-label">Current version release date</label>
|
<label for="currentReleaseDate" class="control-label">Current version release date</label>
|
||||||
<input type="text" class="form-control" id="currentReleaseDate" readonly>
|
<input type="text" class="form-control" id="currentReleaseDate" readonly>
|
||||||
|
|
|
@ -56,6 +56,15 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group form-group-sm">
|
||||||
|
<select name="status-selector" id="status-selector" class="custom-select form-control-navbar" style="margin-left: 0.5em; height: 90%; font-size: 14px;">
|
||||||
|
<option value="All">All</option>
|
||||||
|
<option value="Enabled">Enabled</option>
|
||||||
|
<option value="Disabled">Disabled</option>
|
||||||
|
<option value="Connected">Connected</option>
|
||||||
|
<option value="Disconnected">Disconnected</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Right navbar links -->
|
<!-- Right navbar links -->
|
||||||
|
@ -63,7 +72,7 @@
|
||||||
<button style="margin-left: 0.5em;" type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal"
|
<button style="margin-left: 0.5em;" type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal"
|
||||||
data-target="#modal_new_client"><i class="nav-icon fas fa-plus"></i> New
|
data-target="#modal_new_client"><i class="nav-icon fas fa-plus"></i> New
|
||||||
Client</button>
|
Client</button>
|
||||||
<button style="margin-left: 0.5em;" type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal"
|
<button id="apply-config-button" style="margin-left: 0.5em; display: none;" type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal"
|
||||||
data-target="#modal_apply_config"><i class="nav-icon fas fa-check"></i> Apply
|
data-target="#modal_apply_config"><i class="nav-icon fas fa-check"></i> Apply
|
||||||
Config</button>
|
Config</button>
|
||||||
{{if .baseData.CurrentUser}}
|
{{if .baseData.CurrentUser}}
|
||||||
|
@ -90,7 +99,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
{{if .baseData.CurrentUser}}
|
{{if .baseData.CurrentUser}}
|
||||||
<a href="{{.basePath}}/profile" class="d-block">{{.baseData.CurrentUser}}</a>
|
|
||||||
|
{{if .baseData.Admin}}
|
||||||
|
<a href="{{.basePath}}/profile" class="d-block">Administrator: {{.baseData.CurrentUser}}</a>
|
||||||
|
{{else}}
|
||||||
|
<a href="{{.basePath}}/profile" class="d-block">Manager: {{.baseData.CurrentUser}}</a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<a href="#" class="d-block">Administrator</a>
|
<a href="#" class="d-block">Administrator</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -109,6 +124,8 @@
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
{{if .baseData.Admin}}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{.basePath}}/wg-server" class="nav-link {{if eq .baseData.Active "wg-server" }}active{{end}}">
|
<a href="{{.basePath}}/wg-server" class="nav-link {{if eq .baseData.Active "wg-server" }}active{{end}}">
|
||||||
<i class="nav-icon fas fa-server"></i>
|
<i class="nav-icon fas fa-server"></i>
|
||||||
|
@ -117,6 +134,8 @@
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
<li class="nav-header">SETTINGS</li>
|
<li class="nav-header">SETTINGS</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{.basePath}}/global-settings" class="nav-link {{if eq .baseData.Active "global-settings" }}active{{end}}">
|
<a href="{{.basePath}}/global-settings" class="nav-link {{if eq .baseData.Active "global-settings" }}active{{end}}">
|
||||||
|
@ -126,6 +145,16 @@
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="{{.basePath}}/users-settings" class="nav-link {{if eq .baseData.Active "users-settings" }}active{{end}}">
|
||||||
|
<i class="nav-icon fas fa-cog"></i>
|
||||||
|
<p>
|
||||||
|
Users Settings
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<li class="nav-header">UTILITIES</li>
|
<li class="nav-header">UTILITIES</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{.basePath}}/status" class="nav-link {{if eq .baseData.Active "status" }}active{{end}}">
|
<a href="{{.basePath}}/status" class="nav-link {{if eq .baseData.Active "status" }}active{{end}}">
|
||||||
|
@ -331,6 +360,32 @@
|
||||||
$('[data-toggle="tooltip"]').tooltip()
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'GET',
|
||||||
|
url: '{{.basePath}}/test-hash',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: "application/json",
|
||||||
|
success: function(data) {
|
||||||
|
if (data.status) {
|
||||||
|
$("#apply-config-button").show()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$("#apply-config-button").hide()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(jqXHR, exception) {
|
||||||
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
toastr.error(responseJson['message']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// populateClient function for render new client info
|
// populateClient function for render new client info
|
||||||
// on the client page.
|
// on the client page.
|
||||||
function populateClient(client_id) {
|
function populateClient(client_id) {
|
||||||
|
|
|
@ -70,17 +70,8 @@ Wireguard Clients
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input type="hidden" id="qr_client_id" name="qr_client_id">
|
<input type="hidden" id="qr_client_id" name="qr_client_id">
|
||||||
<a href="" download="" id="qr_code_a">
|
<img id="qr_code" class="w-100" style="image-rendering: pixelated;" src="" alt="QR code" />
|
||||||
<img id="qr_code" class="w-100" style="image-rendering: pixelated;" src="" alt="QR code" />
|
<!-- do not include FwMark in any client configs: it is INVALID. -->
|
||||||
</a>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="icheck-primary d-inline">
|
|
||||||
<input type="checkbox" id="qr_include_fwmark" onchange="regenerateQRCode()">
|
|
||||||
<label for="qr_include_fwmark">
|
|
||||||
Include FwMark
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.modal-content -->
|
<!-- /.modal-content -->
|
||||||
|
@ -263,6 +254,7 @@ Wireguard Clients
|
||||||
|
|
||||||
// hide all clients and display only the ones that meet the search criteria (name, email, IP)
|
// hide all clients and display only the ones that meet the search criteria (name, email, IP)
|
||||||
$('#search-input').keyup(function () {
|
$('#search-input').keyup(function () {
|
||||||
|
$("#status-selector").val("All");
|
||||||
var query = $(this).val();
|
var query = $(this).val();
|
||||||
$('.col-lg-4').hide();
|
$('.col-lg-4').hide();
|
||||||
$(".info-box-text").each(function() {
|
$(".info-box-text").each(function() {
|
||||||
|
@ -274,6 +266,70 @@ Wireguard Clients
|
||||||
$(".badge-secondary").filter(':contains("' + query + '")').parent().parent().parent().show();
|
$(".badge-secondary").filter(':contains("' + query + '")').parent().parent().parent().show();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$("#status-selector").on('change', function () {
|
||||||
|
$('#search-input').val("");
|
||||||
|
switch ($("#status-selector").val()) {
|
||||||
|
case "All":
|
||||||
|
$('.col-lg-4').show();
|
||||||
|
break;
|
||||||
|
case "Enabled":
|
||||||
|
$('.col-lg-4').hide();
|
||||||
|
$('[id^="paused_"]').each(function () {
|
||||||
|
if ($(this).css("visibility") === "hidden") {
|
||||||
|
$(this).parent().parent().show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "Disabled":
|
||||||
|
$('.col-lg-4').hide();
|
||||||
|
$('[id^="paused_"]').each(function () {
|
||||||
|
if ($(this).css("visibility") !== "hidden") {
|
||||||
|
$(this).parent().parent().show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "Connected":
|
||||||
|
$('.col-lg-4').hide();
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'GET',
|
||||||
|
url: '{{.basePath}}/status',
|
||||||
|
success: function (data) {
|
||||||
|
const returnedHTML = $(data).find(".table-success").get();
|
||||||
|
var returnedString = "";
|
||||||
|
returnedHTML.forEach(entry => returnedString += entry.outerHTML);
|
||||||
|
$(".fa-key").each(function () {
|
||||||
|
if (returnedString.indexOf($(this).parent().text().trim()) != -1) {
|
||||||
|
$(this).closest('.col-lg-4').show();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "Disconnected":
|
||||||
|
$('.col-lg-4').show();
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'GET',
|
||||||
|
url: '{{.basePath}}/status',
|
||||||
|
success: function (data) {
|
||||||
|
const returnedHTML = $(data).find(".table-success").get();
|
||||||
|
var returnedString = "";
|
||||||
|
returnedHTML.forEach(entry => returnedString += entry.outerHTML);
|
||||||
|
$(".fa-key").each(function () {
|
||||||
|
if (returnedString.indexOf($(this).parent().text().trim()) != -1) {
|
||||||
|
$(this).closest('.col-lg-4').hide();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$('.col-lg-4').show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// modal_pause_client modal event
|
// modal_pause_client modal event
|
||||||
$("#modal_pause_client").on('show.bs.modal', function (event) {
|
$("#modal_pause_client").on('show.bs.modal', function (event) {
|
||||||
const button = $(event.relatedTarget);
|
const button = $(event.relatedTarget);
|
||||||
|
@ -425,7 +481,7 @@ Wireguard Clients
|
||||||
cache: false,
|
cache: false,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '{{.basePath}}/api/client/' + client_id,
|
url: '{{.basePath}}/api/client/' + client_id,
|
||||||
data: {
|
data: {
|
||||||
qrCodeIncludeFwMark: include_fwmark
|
qrCodeIncludeFwMark: include_fwmark
|
||||||
},
|
},
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
|
|
|
@ -56,10 +56,10 @@ Global Settings
|
||||||
value="{{if .globalSettings.PersistentKeepalive }}{{ .globalSettings.PersistentKeepalive }}{{end}}">
|
value="{{if .globalSettings.PersistentKeepalive }}{{ .globalSettings.PersistentKeepalive }}{{end}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="forward_mark">Forward Mark</label>
|
<label for="firewall_mark">Firewall Mark</label>
|
||||||
<input type="text" class="form-control" id="forward_mark"
|
<input type="text" class="form-control" id="firewall_mark"
|
||||||
name="forward_mark" placeholder="Forward Mark"
|
name="firewall_mark" placeholder="Firewall Mark"
|
||||||
value="{{ .globalSettings.ForwardMark }}">
|
value="{{ .globalSettings.FirewallMark }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="config_file_path">Wireguard Config File Path</label>
|
<label for="config_file_path">Wireguard Config File Path</label>
|
||||||
|
@ -100,8 +100,8 @@ Global Settings
|
||||||
until they reach out to other peers themselves. Adding <code>PersistentKeepalive</code>
|
until they reach out to other peers themselves. Adding <code>PersistentKeepalive</code>
|
||||||
can ensure that the connection remains open.</dd>
|
can ensure that the connection remains open.</dd>
|
||||||
<dd>Leave blank to omit this setting in the Client config.</dd>
|
<dd>Leave blank to omit this setting in the Client config.</dd>
|
||||||
<dt>5. Forward Mark</dt>
|
<dt>5. Firewall Mark</dt>
|
||||||
<dd>Set an <code>fwmark</code> on all packets going out of WireGuard's UDP socket. Default value: <code>0xca6c</code></dd>
|
<dd>Add a matching <code>fwmark</code> on all packets going out of a WireGuard non-default-route tunnel. Default value: <code>0xca6c</code></dd>
|
||||||
<dt>6. Wireguard Config File Path</dt>
|
<dt>6. Wireguard Config File Path</dt>
|
||||||
<dd>The path of your Wireguard server config file. Please make sure the parent directory
|
<dd>The path of your Wireguard server config file. Please make sure the parent directory
|
||||||
exists and is writable.</dd>
|
exists and is writable.</dd>
|
||||||
|
@ -149,9 +149,9 @@ Global Settings
|
||||||
const dns_servers = $("#dns_servers").val().split(",");
|
const dns_servers = $("#dns_servers").val().split(",");
|
||||||
const mtu = $("#mtu").val();
|
const mtu = $("#mtu").val();
|
||||||
const persistent_keepalive = $("#persistent_keepalive").val();
|
const persistent_keepalive = $("#persistent_keepalive").val();
|
||||||
const forward_mark = $("#forward_mark").val();
|
const firewall_mark = $("#firewall_mark").val();
|
||||||
const config_file_path = $("#config_file_path").val();
|
const config_file_path = $("#config_file_path").val();
|
||||||
const data = {"endpoint_address": endpoint_address, "dns_servers": dns_servers, "mtu": mtu, "persistent_keepalive": persistent_keepalive, "forward_mark": forward_mark, "config_file_path": config_file_path};
|
const data = {"endpoint_address": endpoint_address, "dns_servers": dns_servers, "mtu": mtu, "persistent_keepalive": persistent_keepalive, "firewall_mark": firewall_mark, "config_file_path": config_file_path};
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
cache: false,
|
cache: false,
|
||||||
|
@ -222,7 +222,7 @@ Global Settings
|
||||||
config_file_path: {
|
config_file_path: {
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
forward_mark: {
|
firewall_mark: {
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -101,7 +101,11 @@
|
||||||
$("#btn_login").click(function () {
|
$("#btn_login").click(function () {
|
||||||
const username = $("#username").val();
|
const username = $("#username").val();
|
||||||
const password = $("#password").val();
|
const password = $("#password").val();
|
||||||
const data = {"username": username, "password": password}
|
let rememberMe = false;
|
||||||
|
if ($("#remember").is(':checked')){
|
||||||
|
rememberMe = true;
|
||||||
|
}
|
||||||
|
const data = {"username": username, "password": password, "rememberMe": rememberMe}
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
cache: false,
|
cache: false,
|
||||||
|
|
|
@ -31,7 +31,7 @@ Profile
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username" class="control-label">Username</label>
|
<label for="username" class="control-label">Username</label>
|
||||||
<input type="text" class="form-control" name="username" id="username"
|
<input type="text" class="form-control" name="username" id="username"
|
||||||
value="{{ .userInfo.Username }}">
|
value="">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password" class="control-label">Password</label>
|
<label for="password" class="control-label">Password</label>
|
||||||
|
@ -55,56 +55,82 @@ Profile
|
||||||
|
|
||||||
{{ define "bottom_js"}}
|
{{ define "bottom_js"}}
|
||||||
<script>
|
<script>
|
||||||
function updateUserInfo() {
|
{
|
||||||
const username = $("#username").val();
|
var previous_username;
|
||||||
const password = $("#password").val();
|
var admin;
|
||||||
const data = {"username": username, "password": password};
|
}
|
||||||
$.ajax({
|
$(document).ready(function () {
|
||||||
cache: false,
|
$.ajax({
|
||||||
method: 'POST',
|
cache: false,
|
||||||
url: '{{.basePath}}/profile',
|
method: 'GET',
|
||||||
dataType: 'json',
|
url: '{{.basePath}}/api/user/{{.baseData.CurrentUser}}',
|
||||||
contentType: "application/json",
|
dataType: 'json',
|
||||||
data: JSON.stringify(data),
|
contentType: "application/json",
|
||||||
success: function (data) {
|
success: function (resp) {
|
||||||
toastr.success("Updated admin user information successfully");
|
const user = resp;
|
||||||
},
|
$("#username").val(user.username);
|
||||||
error: function (jqXHR, exception) {
|
previous_username = user.username;
|
||||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
admin = user.admin;
|
||||||
toastr.error(responseJson['message']);
|
},
|
||||||
}
|
error: function (jqXHR, exception) {
|
||||||
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
toastr.error(responseJson['message']);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
$.validator.setDefaults({
|
function updateUserInfo() {
|
||||||
submitHandler: function () {
|
const username = $("#username").val();
|
||||||
updateUserInfo();
|
const password = $("#password").val();
|
||||||
}
|
const data = {"username": username, "password": password, "previous_username": previous_username, "admin":admin};
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'POST',
|
||||||
|
url: '{{.basePath}}/update-user',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: "application/json",
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: function (data) {
|
||||||
|
toastr.success("Updated user information successfully");
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
|
error: function (jqXHR, exception) {
|
||||||
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
toastr.error(responseJson['message']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
$.validator.setDefaults({
|
||||||
|
submitHandler: function () {
|
||||||
|
updateUserInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#frm_profile").validate({
|
||||||
|
rules: {
|
||||||
|
username: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
username: {
|
||||||
|
required: "Please enter a username",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
errorElement: 'span',
|
||||||
|
errorPlacement: function (error, element) {
|
||||||
|
error.addClass('invalid-feedback');
|
||||||
|
element.closest('.form-group').append(error);
|
||||||
|
},
|
||||||
|
highlight: function (element, errorClass, validClass) {
|
||||||
|
$(element).addClass('is-invalid');
|
||||||
|
},
|
||||||
|
unhighlight: function (element, errorClass, validClass) {
|
||||||
|
$(element).removeClass('is-invalid');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
$("#frm_profile").validate({
|
|
||||||
rules: {
|
|
||||||
username: {
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
messages: {
|
|
||||||
username: {
|
|
||||||
required: "Please enter a username",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
errorElement: 'span',
|
|
||||||
errorPlacement: function (error, element) {
|
|
||||||
error.addClass('invalid-feedback');
|
|
||||||
element.closest('.form-group').append(error);
|
|
||||||
},
|
|
||||||
highlight: function (element, errorClass, validClass) {
|
|
||||||
$(element).addClass('is-invalid');
|
|
||||||
},
|
|
||||||
unhighlight: function (element, errorClass, validClass) {
|
|
||||||
$(element).removeClass('is-invalid');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
294
templates/users_settings.html
Normal file
294
templates/users_settings.html
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
{{define "title"}}
|
||||||
|
Users Settings
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "top_css"}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "username"}}
|
||||||
|
{{ .username }}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "page_title"}}
|
||||||
|
Users Settings
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "page_content"}}
|
||||||
|
<section class="content">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row" id="users-list">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="modal fade" id="modal_edit_user">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">Edit User</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form name="frm_edit_user" id="frm_edit_user">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group" style="display:none">
|
||||||
|
<input type="text" style="display:none" class="form-control" id="_previous_user_name"
|
||||||
|
name="_previous_user_name">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="_user_name" class="control-label">Name</label>
|
||||||
|
<input type="text" class="form-control" id="_user_name" name="_user_name">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="_user_password" class="control-label">Password</label>
|
||||||
|
<input type="password" class="form-control" id="_user_password" name="_user_password" value=""
|
||||||
|
placeholder="Leave empty to keep the password unchanged">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="icheck-primary d-inline">
|
||||||
|
<input type="checkbox" id="_admin">
|
||||||
|
<label for="_admin">
|
||||||
|
Admin
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer justify-content-between">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-success">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- /.modal-content -->
|
||||||
|
</div>
|
||||||
|
<!-- /.modal-dialog -->
|
||||||
|
</div>
|
||||||
|
<!-- /.modal -->
|
||||||
|
|
||||||
|
<div class="modal fade" id="modal_remove_user">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content bg-danger">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">Remove</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer justify-content-between">
|
||||||
|
<button type="button" class="btn btn-outline-dark" data-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-outline-dark" id="remove_user_confirm">Apply</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /.modal-content -->
|
||||||
|
</div>
|
||||||
|
<!-- /.modal-dialog -->
|
||||||
|
</div>
|
||||||
|
<!-- /.modal -->
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "bottom_js"}}
|
||||||
|
<script>
|
||||||
|
function populateUsersList() {
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'GET',
|
||||||
|
url: '{{.basePath}}/getusers',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: "application/json",
|
||||||
|
success: function (data) {
|
||||||
|
renderUserList(data);
|
||||||
|
},
|
||||||
|
error: function (jqXHR, exception) {
|
||||||
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
toastr.error(responseJson['message']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// load user list
|
||||||
|
$(document).ready(function () {
|
||||||
|
populateUsersList();
|
||||||
|
let newUserHtml = '<div class="col-sm-2 offset-md-4" style=" text-align: right;">' +
|
||||||
|
'<button style="" id="btn_new_user" type="button" class="btn btn-outline-primary btn-sm" ' +
|
||||||
|
'data-toggle="modal" data-target="#modal_edit_user" data-username="">' +
|
||||||
|
'<i class="nav-icon fas fa-plus"></i> New User</button></div>';
|
||||||
|
$('h1').parents(".row").append(newUserHtml);
|
||||||
|
})
|
||||||
|
|
||||||
|
// modal_remove_user modal event
|
||||||
|
$("#modal_remove_user").on('show.bs.modal', function (event) {
|
||||||
|
const button = $(event.relatedTarget);
|
||||||
|
const user_name = button.data('username');
|
||||||
|
const modal = $(this);
|
||||||
|
modal.find('.modal-body').text("You are about to remove user " + user_name);
|
||||||
|
modal.find('#remove_user_confirm').val(user_name);
|
||||||
|
})
|
||||||
|
|
||||||
|
// remove_user_confirm button event
|
||||||
|
$(document).ready(function () {
|
||||||
|
$("#remove_user_confirm").click(function () {
|
||||||
|
const user_name = $(this).val();
|
||||||
|
const data = {"username": user_name};
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'POST',
|
||||||
|
url: '{{.basePath}}/remove-user',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: "application/json",
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: function (data) {
|
||||||
|
$("#modal_remove_user").modal('hide');
|
||||||
|
toastr.success('Removed user successfully');
|
||||||
|
const divElement = document.getElementById('user_' + user_name);
|
||||||
|
divElement.style.display = "none";
|
||||||
|
location.reload()
|
||||||
|
},
|
||||||
|
error: function (jqXHR, exception) {
|
||||||
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
toastr.error(responseJson['message']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Edit user modal event
|
||||||
|
$(document).ready(function () {
|
||||||
|
$("#modal_edit_user").on('show.bs.modal', function (event) {
|
||||||
|
let modal = $(this);
|
||||||
|
const button = $(event.relatedTarget);
|
||||||
|
const user_name = button.data('username');
|
||||||
|
|
||||||
|
// update user modal data
|
||||||
|
if (user_name !== "") {
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'GET',
|
||||||
|
url: '{{.basePath}}/api/user/' + user_name,
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: "application/json",
|
||||||
|
success: function (resp) {
|
||||||
|
const user = resp;
|
||||||
|
|
||||||
|
modal.find(".modal-title").text("Edit user " + user.username);
|
||||||
|
modal.find("#_user_name").val(user.username);
|
||||||
|
modal.find("#_previous_user_name").val(user.username);
|
||||||
|
modal.find("#_user_password").val("");
|
||||||
|
modal.find("#_user_password").prop("placeholder", "Leave empty to keep the password unchanged")
|
||||||
|
modal.find("#_admin").prop("checked", user.admin);
|
||||||
|
},
|
||||||
|
error: function (jqXHR, exception) {
|
||||||
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
toastr.error(responseJson['message']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
modal.find(".modal-title").text("Add new user");
|
||||||
|
modal.find("#_user_name").val("");
|
||||||
|
modal.find("#_previous_user_name").val("");
|
||||||
|
modal.find("#_user_password").val("");
|
||||||
|
modal.find("#_user_password").prop("placeholder", "")
|
||||||
|
modal.find("#_admin").prop("checked", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateUserInfo() {
|
||||||
|
const username = $("#_user_name").val();
|
||||||
|
const previous_username = $("#_previous_user_name").val();
|
||||||
|
const password = $("#_user_password").val();
|
||||||
|
let admin = false;
|
||||||
|
if ($("#_admin").is(':checked')) {
|
||||||
|
admin = true;
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
"previous_username": previous_username,
|
||||||
|
"admin": admin
|
||||||
|
};
|
||||||
|
|
||||||
|
if (previous_username !== "") {
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'POST',
|
||||||
|
url: '{{.basePath}}/update-user',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: "application/json",
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: function (data) {
|
||||||
|
toastr.success("Updated user information successfully");
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
|
error: function (jqXHR, exception) {
|
||||||
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
toastr.error(responseJson['message']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'POST',
|
||||||
|
url: '{{.basePath}}/create-user',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: "application/json",
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: function (data) {
|
||||||
|
toastr.success("Created user successfully");
|
||||||
|
location.reload();
|
||||||
|
},
|
||||||
|
error: function (jqXHR, exception) {
|
||||||
|
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||||
|
toastr.error(responseJson['message']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
$.validator.setDefaults({
|
||||||
|
submitHandler: function (form) {
|
||||||
|
updateUserInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Edit user form validation
|
||||||
|
$("#frm_edit_user").validate({
|
||||||
|
rules: {
|
||||||
|
_user_name: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
_user_password: {
|
||||||
|
required: function () {
|
||||||
|
return $("#_previous_user_name").val() === "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
_user_name: {
|
||||||
|
required: "Please enter a username"
|
||||||
|
},
|
||||||
|
_user_password: {
|
||||||
|
required: "Please input a password"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errorElement: 'span',
|
||||||
|
errorPlacement: function (error, element) {
|
||||||
|
error.addClass('invalid-feedback');
|
||||||
|
element.closest('.form-group').append(error);
|
||||||
|
},
|
||||||
|
highlight: function (element, errorClass, validClass) {
|
||||||
|
$(element).addClass('is-invalid');
|
||||||
|
},
|
||||||
|
unhighlight: function (element, errorClass, validClass) {
|
||||||
|
$(element).removeClass('is-invalid');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
|
@ -25,12 +25,13 @@ var (
|
||||||
const (
|
const (
|
||||||
DefaultUsername = "admin"
|
DefaultUsername = "admin"
|
||||||
DefaultPassword = "admin"
|
DefaultPassword = "admin"
|
||||||
|
DefaultIsAdmin = true
|
||||||
DefaultServerAddress = "10.252.1.0/24"
|
DefaultServerAddress = "10.252.1.0/24"
|
||||||
DefaultServerPort = 51820
|
DefaultServerPort = 51820
|
||||||
DefaultDNS = "1.1.1.1"
|
DefaultDNS = "1.1.1.1"
|
||||||
DefaultMTU = 1450
|
DefaultMTU = 1450
|
||||||
DefaultPersistentKeepalive = 15
|
DefaultPersistentKeepalive = 15
|
||||||
DefaultForwardMark = "0xca6c"
|
DefaultFirewallMark = "0xca6c" // i.e. 51820
|
||||||
DefaultConfigFilePath = "/etc/wireguard/wg0.conf"
|
DefaultConfigFilePath = "/etc/wireguard/wg0.conf"
|
||||||
UsernameEnvVar = "WGUI_USERNAME"
|
UsernameEnvVar = "WGUI_USERNAME"
|
||||||
PasswordEnvVar = "WGUI_PASSWORD"
|
PasswordEnvVar = "WGUI_PASSWORD"
|
||||||
|
@ -40,8 +41,9 @@ const (
|
||||||
DNSEnvVar = "WGUI_DNS"
|
DNSEnvVar = "WGUI_DNS"
|
||||||
MTUEnvVar = "WGUI_MTU"
|
MTUEnvVar = "WGUI_MTU"
|
||||||
PersistentKeepaliveEnvVar = "WGUI_PERSISTENT_KEEPALIVE"
|
PersistentKeepaliveEnvVar = "WGUI_PERSISTENT_KEEPALIVE"
|
||||||
ForwardMarkEnvVar = "WGUI_FORWARD_MARK"
|
FirewallMarkEnvVar = "WGUI_FIREWALL_MARK"
|
||||||
ConfigFilePathEnvVar = "WGUI_CONFIG_FILE_PATH"
|
ConfigFilePathEnvVar = "WGUI_CONFIG_FILE_PATH"
|
||||||
|
LogLevel = "WGUI_LOG_LEVEL"
|
||||||
ServerAddressesEnvVar = "WGUI_SERVER_INTERFACE_ADDRESSES"
|
ServerAddressesEnvVar = "WGUI_SERVER_INTERFACE_ADDRESSES"
|
||||||
ServerListenPortEnvVar = "WGUI_SERVER_LISTEN_PORT"
|
ServerListenPortEnvVar = "WGUI_SERVER_LISTEN_PORT"
|
||||||
ServerPostUpScriptEnvVar = "WGUI_SERVER_POST_UP_SCRIPT"
|
ServerPostUpScriptEnvVar = "WGUI_SERVER_POST_UP_SCRIPT"
|
||||||
|
|
89
util/util.go
89
util/util.go
|
@ -4,15 +4,20 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ngoduykhanh/wireguard-ui/store"
|
||||||
|
"golang.org/x/mod/sumdb/dirhash"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
rice "github.com/GeertJohan/go.rice"
|
|
||||||
externalip "github.com/glendc/go-external-ip"
|
externalip "github.com/glendc/go-external-ip"
|
||||||
"github.com/labstack/gommon/log"
|
"github.com/labstack/gommon/log"
|
||||||
"github.com/alikhanich/wireguard-ui/model"
|
"github.com/alikhanich/wireguard-ui/model"
|
||||||
|
@ -60,18 +65,12 @@ func BuildClientConfig(client model.Client, server model.Server, setting model.G
|
||||||
peerPersistentKeepalive = fmt.Sprintf("PersistentKeepalive = %d\n", setting.PersistentKeepalive)
|
peerPersistentKeepalive = fmt.Sprintf("PersistentKeepalive = %d\n", setting.PersistentKeepalive)
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardMark := ""
|
|
||||||
if setting.ForwardMark != "" {
|
|
||||||
forwardMark = fmt.Sprintf("FwMark = %s\n", setting.ForwardMark)
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the config as string
|
// build the config as string
|
||||||
strConfig := "[Interface]\n" +
|
strConfig := "[Interface]\n" +
|
||||||
clientAddress +
|
clientAddress +
|
||||||
clientPrivateKey +
|
clientPrivateKey +
|
||||||
clientDNS +
|
clientDNS +
|
||||||
clientMTU +
|
clientMTU +
|
||||||
forwardMark +
|
|
||||||
"\n[Peer]\n" +
|
"\n[Peer]\n" +
|
||||||
peerPublicKey +
|
peerPublicKey +
|
||||||
peerPresharedKey +
|
peerPresharedKey +
|
||||||
|
@ -221,10 +220,12 @@ func GetPublicIP() (model.Interface, error) {
|
||||||
ip, err := consensus.ExternalIP()
|
ip, err := consensus.ExternalIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
publicInterface.IPAddress = "N/A"
|
publicInterface.IPAddress = "N/A"
|
||||||
|
} else {
|
||||||
|
publicInterface.IPAddress = ip.String()
|
||||||
}
|
}
|
||||||
publicInterface.IPAddress = ip.String()
|
|
||||||
|
|
||||||
return publicInterface, err
|
// error handling happend above, no need to pass it through
|
||||||
|
return publicInterface, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIPFromCIDR get ip from CIDR
|
// GetIPFromCIDR get ip from CIDR
|
||||||
|
@ -381,7 +382,7 @@ func ValidateIPAllocation(serverAddresses []string, ipAllocatedList []string, ip
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteWireGuardServerConfig to write Wireguard server config. e.g. wg0.conf
|
// WriteWireGuardServerConfig to write Wireguard server config. e.g. wg0.conf
|
||||||
func WriteWireGuardServerConfig(tmplBox *rice.Box, serverConfig model.Server, clientDataList []model.ClientData, globalSettings model.GlobalSetting) error {
|
func WriteWireGuardServerConfig(tmplDir fs.FS, serverConfig model.Server, clientDataList []model.ClientData, usersList []model.User, globalSettings model.GlobalSetting) error {
|
||||||
var tmplWireguardConf string
|
var tmplWireguardConf string
|
||||||
|
|
||||||
// if set, read wg.conf template from WgConfTemplate
|
// if set, read wg.conf template from WgConfTemplate
|
||||||
|
@ -393,7 +394,7 @@ func WriteWireGuardServerConfig(tmplBox *rice.Box, serverConfig model.Server, cl
|
||||||
tmplWireguardConf = string(fileContentBytes)
|
tmplWireguardConf = string(fileContentBytes)
|
||||||
} else {
|
} else {
|
||||||
// read default wg.conf template file to string
|
// read default wg.conf template file to string
|
||||||
fileContent, err := tmplBox.String("wg.conf")
|
fileContent, err := StringFromEmbedFile(tmplDir, "wg.conf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -416,6 +417,7 @@ func WriteWireGuardServerConfig(tmplBox *rice.Box, serverConfig model.Server, cl
|
||||||
"serverConfig": serverConfig,
|
"serverConfig": serverConfig,
|
||||||
"clientDataList": clientDataList,
|
"clientDataList": clientDataList,
|
||||||
"globalSettings": globalSettings,
|
"globalSettings": globalSettings,
|
||||||
|
"usersList": usersList,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.Execute(f, config)
|
err = t.Execute(f, config)
|
||||||
|
@ -462,3 +464,68 @@ func LookupEnvOrStrings(key string, defaultVal []string) []string {
|
||||||
}
|
}
|
||||||
return defaultVal
|
return defaultVal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StringFromEmbedFile(embed fs.FS, filename string) (string, error) {
|
||||||
|
file, err := embed.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
content, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(content), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseLogLevel(lvl string) (log.Lvl, error) {
|
||||||
|
switch strings.ToLower(lvl) {
|
||||||
|
case "debug":
|
||||||
|
return log.DEBUG, nil
|
||||||
|
case "info":
|
||||||
|
return log.INFO, nil
|
||||||
|
case "warn":
|
||||||
|
return log.WARN, nil
|
||||||
|
case "error":
|
||||||
|
return log.ERROR, nil
|
||||||
|
case "off":
|
||||||
|
return log.OFF, nil
|
||||||
|
default:
|
||||||
|
return log.DEBUG, fmt.Errorf("not a valid log level: %s", lvl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentHash returns current hashes
|
||||||
|
func GetCurrentHash(db store.IStore) (string, string) {
|
||||||
|
hashClients, _ := dirhash.HashDir(path.Join(db.GetPath(), "clients"), "prefix", dirhash.Hash1)
|
||||||
|
files := append([]string(nil), "prefix/global_settings.json", "prefix/interfaces.json", "prefix/keypair.json")
|
||||||
|
|
||||||
|
osOpen := func(name string) (io.ReadCloser, error) {
|
||||||
|
return os.Open(filepath.Join(path.Join(db.GetPath(), "server"), strings.TrimPrefix(name, "prefix")))
|
||||||
|
}
|
||||||
|
hashServer, _ := dirhash.Hash1(files, osOpen)
|
||||||
|
|
||||||
|
return hashClients, hashServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func HashesChanged(db store.IStore) bool {
|
||||||
|
old, _ := db.GetHashes()
|
||||||
|
oldClient := old.Client
|
||||||
|
oldServer := old.Server
|
||||||
|
newClient, newServer := GetCurrentHash(db)
|
||||||
|
|
||||||
|
if oldClient != newClient {
|
||||||
|
//fmt.Println("Hash for client differs")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if oldServer != newServer {
|
||||||
|
//fmt.Println("Hash for server differs")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateHashes(db store.IStore) error {
|
||||||
|
var clientServerHashes model.ClientServerHashes
|
||||||
|
clientServerHashes.Client, clientServerHashes.Server = GetCurrentHash(db)
|
||||||
|
return db.SaveHashes(clientServerHashes)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue