mirror of
https://github.com/vx3r/wg-gen-web.git
synced 2025-01-05 03:13:24 +00:00
oauth2 oidc, vuex store
This commit is contained in:
parent
f90124afbf
commit
125ddaef0f
35
.env
35
.env
@ -1,12 +1,41 @@
|
|||||||
|
# IP address to listen to
|
||||||
SERVER=0.0.0.0
|
SERVER=0.0.0.0
|
||||||
|
# port to bind
|
||||||
PORT=8080
|
PORT=8080
|
||||||
|
# Gin framework release mode
|
||||||
GIN_MODE=release
|
GIN_MODE=release
|
||||||
|
# where to write all generated config files
|
||||||
WG_CONF_DIR=./wireguard
|
WG_CONF_DIR=./wireguard
|
||||||
|
# WireGuard main config file name, generally <interface name>.conf
|
||||||
WG_INTERFACE_NAME=wg0.conf
|
WG_INTERFACE_NAME=wg0.conf
|
||||||
|
|
||||||
|
# SMTP settings to send email to clients
|
||||||
SMTP_HOST=smtp.gmail.com
|
SMTP_HOST=smtp.gmail.com
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_USERNAME=account@gmail.com
|
SMTP_USERNAME=account@gmail.com
|
||||||
SMTP_PASSWORD="*************"
|
SMTP_PASSWORD=*************
|
||||||
SMTP_FROM="Wg Gen Web <account@gmail.com>"
|
SMTP_FROM=Wg Gen Web <account@gmail.com>
|
||||||
|
|
||||||
|
# example with gitlab, which is RFC implementation and no need any custom stuff
|
||||||
|
OAUTH2_PROVIDER_NAME=oauth2oidc
|
||||||
|
OAUTH2_PROVIDER=https://gitlab.com
|
||||||
|
OAUTH2_CLIENT_ID=
|
||||||
|
OAUTH2_CLIENT_SECRET=
|
||||||
|
OAUTH2_REDIRECT_URL=https://wg-gen-web-demo.127-0-0-1.fr
|
||||||
|
|
||||||
|
# example with google
|
||||||
|
OAUTH2_PROVIDER_NAME=google
|
||||||
|
OAUTH2_PROVIDER=
|
||||||
|
OAUTH2_CLIENT_ID=
|
||||||
|
OAUTH2_CLIENT_SECRET=
|
||||||
|
OAUTH2_REDIRECT_URL=
|
||||||
|
|
||||||
|
# example with github
|
||||||
|
OAUTH2_PROVIDER_NAME=github
|
||||||
|
OAUTH2_PROVIDER=
|
||||||
|
OAUTH2_CLIENT_ID=
|
||||||
|
OAUTH2_CLIENT_SECRET=
|
||||||
|
OAUTH2_REDIRECT_URL=
|
||||||
|
|
||||||
|
# set provider name to fake to disable auth, also the default
|
||||||
|
OAUTH2_PROVIDER_NAME=fake
|
||||||
|
240
api/api.go
240
api/api.go
@ -2,244 +2,12 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
log "github.com/sirupsen/logrus"
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api/v1"
|
||||||
"github.com/skip2/go-qrcode"
|
|
||||||
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/core"
|
|
||||||
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
|
|
||||||
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/template"
|
|
||||||
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/version"
|
|
||||||
"net/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ApplyRoutes applies router to gin Router
|
func ApplyRoutes(r *gin.Engine, private bool) {
|
||||||
func ApplyRoutes(r *gin.Engine) {
|
api := r.Group("/api")
|
||||||
client := r.Group("/api/v1.0/client")
|
|
||||||
{
|
{
|
||||||
|
apiv1.ApplyRoutes(api, private)
|
||||||
client.POST("", createClient)
|
|
||||||
client.GET("/:id", readClient)
|
|
||||||
client.PATCH("/:id", updateClient)
|
|
||||||
client.DELETE("/:id", deleteClient)
|
|
||||||
client.GET("", readClients)
|
|
||||||
client.GET("/:id/config", configClient)
|
|
||||||
client.GET("/:id/email", emailClient)
|
|
||||||
}
|
|
||||||
|
|
||||||
server := r.Group("/api/v1.0/server")
|
|
||||||
{
|
|
||||||
server.GET("", readServer)
|
|
||||||
server.PATCH("", updateServer)
|
|
||||||
server.GET("/config", configServer)
|
|
||||||
server.GET("/version", versionStr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createClient(c *gin.Context) {
|
|
||||||
var data model.Client
|
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to bind")
|
|
||||||
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := core.CreateClient(&data)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to create client")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, client)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readClient(c *gin.Context) {
|
|
||||||
id := c.Param("id")
|
|
||||||
|
|
||||||
client, err := core.ReadClient(id)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to read client")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, client)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateClient(c *gin.Context) {
|
|
||||||
var data model.Client
|
|
||||||
id := c.Param("id")
|
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to bind")
|
|
||||||
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := core.UpdateClient(id, &data)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to update client")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, client)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteClient(c *gin.Context) {
|
|
||||||
id := c.Param("id")
|
|
||||||
|
|
||||||
err := core.DeleteClient(id)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to remove client")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func readClients(c *gin.Context) {
|
|
||||||
clients, err := core.ReadClients()
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to list clients")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, clients)
|
|
||||||
}
|
|
||||||
|
|
||||||
func configClient(c *gin.Context) {
|
|
||||||
configData, err := core.ReadClientConfig(c.Param("id"))
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to read client config")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
formatQr := c.DefaultQuery("qrcode", "false")
|
|
||||||
if formatQr == "false" {
|
|
||||||
// return config as txt file
|
|
||||||
c.Header("Content-Disposition", "attachment; filename=wg0.conf")
|
|
||||||
c.Data(http.StatusOK, "application/config", configData)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// return config as png qrcode
|
|
||||||
png, err := qrcode.Encode(string(configData), qrcode.Medium, 250)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to create qrcode")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Data(http.StatusOK, "image/png", png)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func emailClient(c *gin.Context) {
|
|
||||||
id := c.Param("id")
|
|
||||||
|
|
||||||
err := core.EmailClient(id)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to send email to client")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func readServer(c *gin.Context) {
|
|
||||||
client, err := core.ReadServer()
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to read client")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, client)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateServer(c *gin.Context) {
|
|
||||||
var data model.Server
|
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to bind")
|
|
||||||
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := core.UpdateServer(&data)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to update client")
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, client)
|
|
||||||
}
|
|
||||||
|
|
||||||
func configServer(c *gin.Context) {
|
|
||||||
clients, err := core.ReadClients()
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to read clients")
|
|
||||||
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
server, err := core.ReadServer()
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to read server")
|
|
||||||
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
configData, err := template.DumpServerWg(clients, server)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"err": err,
|
|
||||||
}).Error("failed to dump wg config")
|
|
||||||
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// return config as txt file
|
|
||||||
c.Header("Content-Disposition", "attachment; filename=wg0.conf")
|
|
||||||
c.Data(http.StatusOK, "application/config", configData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func versionStr(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"version": version.Version,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
134
api/v1/auth/auth.go
Normal file
134
api/v1/auth/auth.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApplyRoutes applies router to gin Router
|
||||||
|
func ApplyRoutes(r *gin.RouterGroup) {
|
||||||
|
g := r.Group("/auth")
|
||||||
|
{
|
||||||
|
g.GET("/oauth2_url", oauth2_url)
|
||||||
|
g.POST("/oauth2_exchange", oauth2_exchange)
|
||||||
|
g.GET("/user", user)
|
||||||
|
g.GET("/logout", logout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* generate redirect url to get OAuth2 code or let client know that OAuth2 is disabled
|
||||||
|
*/
|
||||||
|
func oauth2_url(c *gin.Context) {
|
||||||
|
cacheDb := c.MustGet("cache").(*cache.Cache)
|
||||||
|
|
||||||
|
state, err := util.GenerateRandomString(32)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to generate state random string")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId, err := util.GenerateRandomString(32)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to generate state random string")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
// save clientId and state so we can retrieve for verification
|
||||||
|
cacheDb.Set(clientId, state, 5*time.Minute)
|
||||||
|
|
||||||
|
oauth2Client := c.MustGet("oauth2Client").(auth.Auth)
|
||||||
|
|
||||||
|
data := &model.Auth{
|
||||||
|
Oauth2: true,
|
||||||
|
ClientId: clientId,
|
||||||
|
State: state,
|
||||||
|
CodeUrl: oauth2Client.CodeUrl(state),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* exchange code and get user infos, if OAuth2 is disable just send fake data
|
||||||
|
*/
|
||||||
|
func oauth2_exchange(c *gin.Context) {
|
||||||
|
var loginVals model.Auth
|
||||||
|
if err := c.ShouldBind(&loginVals); err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("code and state fields are missing")
|
||||||
|
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheDb := c.MustGet("cache").(*cache.Cache)
|
||||||
|
savedState, exists := cacheDb.Get(loginVals.ClientId)
|
||||||
|
|
||||||
|
if !exists || savedState != loginVals.State {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"state": loginVals.State,
|
||||||
|
"savedState": savedState,
|
||||||
|
}).Error("saved state and client provided state mismatch")
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
oauth2Client := c.MustGet("oauth2Client").(auth.Auth)
|
||||||
|
|
||||||
|
oauth2Token, err := oauth2Client.Exchange(loginVals.Code)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to exchange code for token")
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheDb.Delete(loginVals.ClientId)
|
||||||
|
cacheDb.Set(oauth2Token.AccessToken, oauth2Token, cache.DefaultExpiration)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, oauth2Token.AccessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logout(c *gin.Context) {
|
||||||
|
cacheDb := c.MustGet("cache").(*cache.Cache)
|
||||||
|
cacheDb.Delete(c.Request.Header.Get(util.AuthTokenHeaderName))
|
||||||
|
c.JSON(http.StatusOK, gin.H{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func user(c *gin.Context) {
|
||||||
|
cacheDb := c.MustGet("cache").(*cache.Cache)
|
||||||
|
oauth2Token, exists := cacheDb.Get(c.Request.Header.Get(util.AuthTokenHeaderName))
|
||||||
|
|
||||||
|
if exists && oauth2Token.(*oauth2.Token).AccessToken == c.Request.Header.Get(util.AuthTokenHeaderName) {
|
||||||
|
oauth2Client := c.MustGet("oauth2Client").(auth.Auth)
|
||||||
|
user, err := oauth2Client.UserInfo(oauth2Token.(*oauth2.Token))
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to get user from oauth2 AccessToken")
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, user)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"exists": exists,
|
||||||
|
util.AuthTokenHeaderName: c.Request.Header.Get(util.AuthTokenHeaderName),
|
||||||
|
}).Error("oauth2 AccessToken is not recognized")
|
||||||
|
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
}
|
190
api/v1/client/client.go
Normal file
190
api/v1/client/client.go
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/skip2/go-qrcode"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/core"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApplyRoutes applies router to gin Router
|
||||||
|
func ApplyRoutes(r *gin.RouterGroup) {
|
||||||
|
g := r.Group("/client")
|
||||||
|
{
|
||||||
|
|
||||||
|
g.POST("", createClient)
|
||||||
|
g.GET("/:id", readClient)
|
||||||
|
g.PATCH("/:id", updateClient)
|
||||||
|
g.DELETE("/:id", deleteClient)
|
||||||
|
g.GET("", readClients)
|
||||||
|
g.GET("/:id/config", configClient)
|
||||||
|
g.GET("/:id/email", emailClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createClient(c *gin.Context) {
|
||||||
|
var data model.Client
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to bind")
|
||||||
|
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get creation user from token and add to client infos
|
||||||
|
oauth2Token := c.MustGet("oauth2Token").(*oauth2.Token)
|
||||||
|
oauth2Client := c.MustGet("oauth2Client").(auth.Auth)
|
||||||
|
user, err := oauth2Client.UserInfo(oauth2Token)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"oauth2Token": oauth2Token,
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to get user with oauth token")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.CreatedBy = user.Name
|
||||||
|
|
||||||
|
client, err := core.CreateClient(&data)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to create client")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readClient(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
client, err := core.ReadClient(id)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to read client")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateClient(c *gin.Context) {
|
||||||
|
var data model.Client
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to bind")
|
||||||
|
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get update user from token and add to client infos
|
||||||
|
oauth2Token := c.MustGet("oauth2Token").(*oauth2.Token)
|
||||||
|
oauth2Client := c.MustGet("oauth2Client").(auth.Auth)
|
||||||
|
user, err := oauth2Client.UserInfo(oauth2Token)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"oauth2Token": oauth2Token,
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to get user with oauth token")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.UpdatedBy = user.Name
|
||||||
|
|
||||||
|
client, err := core.UpdateClient(id, &data)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to update client")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteClient(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
err := core.DeleteClient(id)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to remove client")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func readClients(c *gin.Context) {
|
||||||
|
clients, err := core.ReadClients()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to list clients")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, clients)
|
||||||
|
}
|
||||||
|
|
||||||
|
func configClient(c *gin.Context) {
|
||||||
|
configData, err := core.ReadClientConfig(c.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to read client config")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
formatQr := c.DefaultQuery("qrcode", "false")
|
||||||
|
if formatQr == "false" {
|
||||||
|
// return config as txt file
|
||||||
|
c.Header("Content-Disposition", "attachment; filename=wg0.conf")
|
||||||
|
c.Data(http.StatusOK, "application/config", configData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// return config as png qrcode
|
||||||
|
png, err := qrcode.Encode(string(configData), qrcode.Medium, 250)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to create qrcode")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Data(http.StatusOK, "image/png", png)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func emailClient(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
err := core.EmailClient(id)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to send email to client")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{})
|
||||||
|
}
|
113
api/v1/server/server.go
Normal file
113
api/v1/server/server.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/core"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/template"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/version"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApplyRoutes applies router to gin Router
|
||||||
|
func ApplyRoutes(r *gin.RouterGroup) {
|
||||||
|
g := r.Group("/server")
|
||||||
|
{
|
||||||
|
g.GET("", readServer)
|
||||||
|
g.PATCH("", updateServer)
|
||||||
|
g.GET("/config", configServer)
|
||||||
|
g.GET("/version", versionStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readServer(c *gin.Context) {
|
||||||
|
client, err := core.ReadServer()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to read client")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateServer(c *gin.Context) {
|
||||||
|
var data model.Server
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to bind")
|
||||||
|
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get update user from token and add to server infos
|
||||||
|
oauth2Token := c.MustGet("oauth2Token").(*oauth2.Token)
|
||||||
|
oauth2Client := c.MustGet("oauth2Client").(auth.Auth)
|
||||||
|
user, err := oauth2Client.UserInfo(oauth2Token)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"oauth2Token": oauth2Token,
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to get user with oauth token")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.UpdatedBy = user.Name
|
||||||
|
|
||||||
|
server, err := core.UpdateServer(&data)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to update client")
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, server)
|
||||||
|
}
|
||||||
|
|
||||||
|
func configServer(c *gin.Context) {
|
||||||
|
clients, err := core.ReadClients()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to read clients")
|
||||||
|
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := core.ReadServer()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to read server")
|
||||||
|
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := template.DumpServerWg(clients, server)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Error("failed to dump wg config")
|
||||||
|
c.AbortWithStatus(http.StatusUnprocessableEntity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// return config as txt file
|
||||||
|
c.Header("Content-Disposition", "attachment; filename=wg0.conf")
|
||||||
|
c.Data(http.StatusOK, "application/config", configData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func versionStr(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"version": version.Version,
|
||||||
|
})
|
||||||
|
}
|
21
api/v1/v1.go
Normal file
21
api/v1/v1.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package apiv1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api/v1/auth"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api/v1/client"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api/v1/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApplyRoutes(r *gin.RouterGroup, private bool) {
|
||||||
|
v1 := r.Group("/v1.0")
|
||||||
|
{
|
||||||
|
if private {
|
||||||
|
client.ApplyRoutes(v1)
|
||||||
|
server.ApplyRoutes(v1)
|
||||||
|
} else {
|
||||||
|
auth.ApplyRoutes(v1)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
auth/auth.go
Normal file
13
auth/auth.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Auth interface {
|
||||||
|
Setup() error
|
||||||
|
CodeUrl(state string) string
|
||||||
|
Exchange(code string) (*oauth2.Token, error)
|
||||||
|
UserInfo(oauth2Token *oauth2.Token) (*model.User, error)
|
||||||
|
}
|
47
auth/fake/fake.go
Normal file
47
auth/fake/fake.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package fake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Fake struct{}
|
||||||
|
|
||||||
|
// Setup validate provider
|
||||||
|
func (o *Fake) Setup() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeUrl get url to redirect client for auth
|
||||||
|
func (o *Fake) CodeUrl(state string) string {
|
||||||
|
return "_magic_string_fake_auth_no_redirect_"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange exchange code for Oauth2 token
|
||||||
|
func (o *Fake) Exchange(code string) (*oauth2.Token, error) {
|
||||||
|
rand, err := util.GenerateRandomString(32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oauth2.Token{
|
||||||
|
AccessToken: rand,
|
||||||
|
TokenType: "",
|
||||||
|
RefreshToken: "",
|
||||||
|
Expiry: time.Time{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo get token user
|
||||||
|
func (o *Fake) UserInfo(oauth2Token *oauth2.Token) (*model.User, error) {
|
||||||
|
return &model.User{
|
||||||
|
Sub: "unknown",
|
||||||
|
Name: "Unknown",
|
||||||
|
Email: "unknown",
|
||||||
|
Profile: "unknown",
|
||||||
|
Issuer: "unknown",
|
||||||
|
IssuedAt: time.Time{},
|
||||||
|
}, nil
|
||||||
|
}
|
1
auth/github/github.go
Normal file
1
auth/github/github.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package github
|
1
auth/google/goolge.go
Normal file
1
auth/google/goolge.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package google
|
108
auth/oauth2oidc/oauth2oidc.go
Normal file
108
auth/oauth2oidc/oauth2oidc.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package oauth2oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/coreos/go-oidc"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Oauth2idc struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
oauth2Config *oauth2.Config
|
||||||
|
oidcProvider *oidc.Provider
|
||||||
|
oidcIDTokenVerifier *oidc.IDTokenVerifier
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setup validate provider
|
||||||
|
func (o *Oauth2idc) Setup() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
oidcProvider, err = oidc.NewProvider(context.TODO(), os.Getenv("OAUTH2_PROVIDER"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oidcIDTokenVerifier = oidcProvider.Verifier(&oidc.Config{
|
||||||
|
ClientID: os.Getenv("OAUTH2_CLIENT_ID"),
|
||||||
|
})
|
||||||
|
|
||||||
|
oauth2Config = &oauth2.Config{
|
||||||
|
ClientID: os.Getenv("OAUTH2_CLIENT_ID"),
|
||||||
|
ClientSecret: os.Getenv("OAUTH2_CLIENT_SECRET"),
|
||||||
|
RedirectURL: os.Getenv("OAUTH2_REDIRECT_URL"),
|
||||||
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
|
Endpoint: oidcProvider.Endpoint(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeUrl get url to redirect client for auth
|
||||||
|
func (o *Oauth2idc) CodeUrl(state string) string {
|
||||||
|
return oauth2Config.AuthCodeURL(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange exchange code for Oauth2 token
|
||||||
|
func (o *Oauth2idc) Exchange(code string) (*oauth2.Token, error) {
|
||||||
|
oauth2Token, err := oauth2Config.Exchange(context.TODO(), code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return oauth2Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo get token user
|
||||||
|
func (o *Oauth2idc) UserInfo(oauth2Token *oauth2.Token) (*model.User, error) {
|
||||||
|
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no id_token field in oauth2 token")
|
||||||
|
}
|
||||||
|
|
||||||
|
iDToken, err := oidcIDTokenVerifier.Verify(context.TODO(), rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo, err := oidcProvider.UserInfo(context.TODO(), oauth2.StaticTokenSource(oauth2Token))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfo struct {
|
||||||
|
Subject string `json:"sub"`
|
||||||
|
Profile string `json:"profile"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
|
||||||
|
claims []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID Token payload is just JSON
|
||||||
|
var claims map[string]interface{}
|
||||||
|
if err := userInfo.Claims(&claims); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get id token claims: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get some infos about user
|
||||||
|
user := &model.User{}
|
||||||
|
user.Sub = userInfo.Subject
|
||||||
|
user.Email = userInfo.Email
|
||||||
|
user.Profile = userInfo.Profile
|
||||||
|
|
||||||
|
if v, found := claims["name"]; found && v != nil {
|
||||||
|
user.Name = v.(string)
|
||||||
|
} else {
|
||||||
|
log.Error("name not found in user info claims")
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Issuer = iDToken.Issuer
|
||||||
|
user.IssuedAt = iDToken.IssuedAt
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
@ -7,13 +7,24 @@ import (
|
|||||||
"github.com/gin-contrib/static"
|
"github.com/gin-contrib/static"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api"
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth/fake"
|
||||||
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/auth/oauth2oidc"
|
||||||
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/core"
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/core"
|
||||||
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util"
|
||||||
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/version"
|
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/version"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cacheDb = cache.New(60*time.Minute, 10*time.Minute)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -80,21 +91,88 @@ func main() {
|
|||||||
// cors middleware
|
// cors middleware
|
||||||
config := cors.DefaultConfig()
|
config := cors.DefaultConfig()
|
||||||
config.AllowAllOrigins = true
|
config.AllowAllOrigins = true
|
||||||
|
config.AddAllowHeaders("Authorization")
|
||||||
app.Use(cors.New(config))
|
app.Use(cors.New(config))
|
||||||
|
|
||||||
// protection middleware
|
// protection middleware
|
||||||
app.Use(helmet.Default())
|
app.Use(helmet.Default())
|
||||||
|
|
||||||
// no route redirect to frontend app
|
// add cache storage to gin app
|
||||||
app.NoRoute(func(c *gin.Context) {
|
app.Use(func(ctx *gin.Context) {
|
||||||
c.Redirect(301, "/index.html")
|
ctx.Set("cache", cacheDb)
|
||||||
|
ctx.Next()
|
||||||
})
|
})
|
||||||
|
|
||||||
// serve static files
|
// serve static files
|
||||||
app.Use(static.Serve("/", static.LocalFile("./ui/dist", false)))
|
app.Use(static.Serve("/", static.LocalFile("./ui/dist", false)))
|
||||||
|
|
||||||
// apply api router
|
// setup Oauth2 client if enabled
|
||||||
api.ApplyRoutes(app)
|
var oauth2Client auth.Auth
|
||||||
|
|
||||||
|
switch os.Getenv("OAUTH2_PROVIDER_NAME") {
|
||||||
|
case "fake":
|
||||||
|
log.Warn("Oauth is set to fake, no actual authentication will be performed")
|
||||||
|
oauth2Client = &fake.Fake{}
|
||||||
|
err = oauth2Client.Setup()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"OAUTH2_PROVIDER_NAME": "oauth2oidc",
|
||||||
|
"err": err,
|
||||||
|
}).Fatal("failed to setup Oauth2")
|
||||||
|
}
|
||||||
|
case "oauth2oidc":
|
||||||
|
log.Warn("Oauth is set to oauth2oidc, must be RFC implementation on server side")
|
||||||
|
oauth2Client = &oauth2oidc.Oauth2idc{}
|
||||||
|
err = oauth2Client.Setup()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"OAUTH2_PROVIDER_NAME": "oauth2oidc",
|
||||||
|
"err": err,
|
||||||
|
}).Fatal("failed to setup Oauth2")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"OAUTH2_PROVIDER_NAME": os.Getenv("OAUTH2_PROVIDER_NAME"),
|
||||||
|
}).Fatal("auth provider name unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("OAUTH2_ENABLE") == "true" {
|
||||||
|
oauth2Client = &oauth2oidc.Oauth2idc{}
|
||||||
|
err = oauth2Client.Setup()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"err": err,
|
||||||
|
}).Fatal("failed to setup Oauth2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.Use(func(ctx *gin.Context) {
|
||||||
|
ctx.Set("oauth2Client", oauth2Client)
|
||||||
|
ctx.Next()
|
||||||
|
})
|
||||||
|
|
||||||
|
// apply api routes public
|
||||||
|
api.ApplyRoutes(app, false)
|
||||||
|
|
||||||
|
// simple middleware to check auth
|
||||||
|
app.Use(func(c *gin.Context) {
|
||||||
|
cacheDb := c.MustGet("cache").(*cache.Cache)
|
||||||
|
|
||||||
|
token := c.Request.Header.Get(util.AuthTokenHeaderName)
|
||||||
|
|
||||||
|
oauth2Token, exists := cacheDb.Get(token)
|
||||||
|
if exists && oauth2Token.(*oauth2.Token).AccessToken == token {
|
||||||
|
// will be accessible in auth endpoints
|
||||||
|
c.Set("oauth2Token", oauth2Token)
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
// apply api router private
|
||||||
|
api.ApplyRoutes(app, true)
|
||||||
|
|
||||||
err = app.Run(fmt.Sprintf("%s:%s", os.Getenv("SERVER"), os.Getenv("PORT")))
|
err = app.Run(fmt.Sprintf("%s:%s", os.Getenv("SERVER"), os.Getenv("PORT")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
11
go.mod
11
go.mod
@ -3,15 +3,26 @@ module gitlab.127-0-0-1.fr/vx3r/wg-gen-web
|
|||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/appleboy/gin-jwt/v2 v2.6.3 // indirect
|
||||||
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
|
||||||
|
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||||
github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e
|
github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e
|
||||||
github.com/gin-contrib/cors v1.3.1
|
github.com/gin-contrib/cors v1.3.1
|
||||||
|
github.com/gin-contrib/sessions v0.0.3
|
||||||
github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2
|
github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2
|
||||||
|
github.com/gin-gonic/contrib v0.0.0-20191209060500-d6e26eeaa607 // indirect
|
||||||
github.com/gin-gonic/gin v1.6.2
|
github.com/gin-gonic/gin v1.6.2
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||||
|
github.com/gorilla/sessions v1.2.0 // indirect
|
||||||
github.com/joho/godotenv v1.3.0
|
github.com/joho/godotenv v1.3.0
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/sirupsen/logrus v1.5.0
|
github.com/sirupsen/logrus v1.5.0
|
||||||
github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
|
github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200324154536-ceff61240acf
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200324154536-ceff61240acf
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
|
gopkg.in/square/go-jose.v2 v2.5.0 // indirect
|
||||||
)
|
)
|
||||||
|
9
model/auth.go
Normal file
9
model/auth.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
Oauth2 bool `json:"oauth2"`
|
||||||
|
ClientId string `json:"clientId"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
State string `json:"state"`
|
||||||
|
CodeUrl string `json:"codeUrl"`
|
||||||
|
}
|
@ -18,6 +18,8 @@ type Client struct {
|
|||||||
Address []string `json:"address"`
|
Address []string `json:"address"`
|
||||||
PrivateKey string `json:"privateKey"`
|
PrivateKey string `json:"privateKey"`
|
||||||
PublicKey string `json:"publicKey"`
|
PublicKey string `json:"publicKey"`
|
||||||
|
CreatedBy string `json:"createdBy"`
|
||||||
|
UpdatedBy string `json:"updatedBy"`
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
Updated time.Time `json:"updated"`
|
Updated time.Time `json:"updated"`
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ type Server struct {
|
|||||||
PostUp string `json:"postUp"`
|
PostUp string `json:"postUp"`
|
||||||
PreDown string `json:"preDown"`
|
PreDown string `json:"preDown"`
|
||||||
PostDown string `json:"postDown"`
|
PostDown string `json:"postDown"`
|
||||||
|
UpdatedBy string `json:"updatedBy"`
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
Updated time.Time `json:"updated"`
|
Updated time.Time `json:"updated"`
|
||||||
}
|
}
|
||||||
|
12
model/user.go
Normal file
12
model/user.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Profile string `json:"profile"`
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
IssuedAt time.Time `json:"issuedAt"`
|
||||||
|
}
|
602
ui/package-lock.json
generated
602
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,9 +11,11 @@
|
|||||||
"is-cidr": "^3.1.0",
|
"is-cidr": "^3.1.0",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
|
"vue-axios": "^2.1.5",
|
||||||
"vue-moment": "^4.1.0",
|
"vue-moment": "^4.1.0",
|
||||||
"vue-router": "^3.1.6",
|
"vue-router": "^3.1.6",
|
||||||
"vuetify": "^2.2.22"
|
"vuetify": "^2.2.22",
|
||||||
|
"vuex": "^3.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-router": "^4.3.1",
|
"@vue/cli-plugin-router": "^4.3.1",
|
||||||
|
129
ui/src/App.vue
129
ui/src/App.vue
@ -1,69 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-app id="inspire">
|
<v-app id="inspire">
|
||||||
|
<Notification v-bind:notification="notification"/>
|
||||||
|
<div v-if="this.isAuthenticated">
|
||||||
|
<Header/>
|
||||||
|
|
||||||
<v-app-bar app>
|
<v-content>
|
||||||
<img class="mr-3" :src="require('./assets/logo.png')" height="50" alt="Wg Gen Web"/>
|
<v-container>
|
||||||
<v-toolbar-title to="/">Wg Gen Web</v-toolbar-title>
|
<router-view />
|
||||||
|
</v-container>
|
||||||
<v-spacer />
|
</v-content>
|
||||||
|
|
||||||
<v-toolbar-items>
|
|
||||||
<v-btn to="/clients">
|
|
||||||
Clients
|
|
||||||
<v-icon right dark>mdi-account-network-outline</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn to="/server">
|
|
||||||
Server
|
|
||||||
<v-icon right dark>mdi-vpn</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-toolbar-items>
|
|
||||||
|
|
||||||
</v-app-bar>
|
|
||||||
|
|
||||||
<v-content>
|
|
||||||
<v-container>
|
|
||||||
<router-view />
|
|
||||||
</v-container>
|
|
||||||
<Notification v-bind:notification="notification"/>
|
|
||||||
</v-content>
|
|
||||||
|
|
||||||
<v-footer app>
|
|
||||||
<v-row justify="start" no-gutters>
|
|
||||||
<v-col cols="12" lg="6" md="12" sm="12">
|
|
||||||
<div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'left'">
|
|
||||||
<small>Copyright © {{ new Date().getFullYear() }}, Wg Gen Web.</small>
|
|
||||||
<small>This work is licensed under a <a class="pr-1 pl-1" href="http://www.wtfpl.net/" target="_blank">WTFPL License.</a></small>
|
|
||||||
</div>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row justify="end" no-gutters>
|
|
||||||
<v-col cols="12" lg="6" md="12" sm="12">
|
|
||||||
<div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'right'">
|
|
||||||
<small>Created with</small>
|
|
||||||
<v-icon class="pr-1 pl-1">mdi-heart</v-icon><span>by</span><a class="pr-2 pl-1" href="mailto:vx3r@127-0-0-1.fr">vx3r</a>
|
|
||||||
<a :href="'https://github.com/vx3r/wg-gen-web/commit/' + version"><kbd>Version: {{ version.substring(0,7) }}</kbd></a>
|
|
||||||
</div>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-footer>
|
|
||||||
|
|
||||||
|
<Footer/>
|
||||||
|
</div>
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {ApiService} from "./services/ApiService";
|
|
||||||
import Notification from './components/Notification'
|
import Notification from './components/Notification'
|
||||||
|
import Header from "./components/Header";
|
||||||
|
import Footer from "./components/Footer";
|
||||||
|
import {mapActions, mapGetters} from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
Footer,
|
||||||
|
Header,
|
||||||
Notification
|
Notification
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
api: null,
|
|
||||||
version:'N/A',
|
|
||||||
notification: {
|
notification: {
|
||||||
show: false,
|
show: false,
|
||||||
color: '',
|
color: '',
|
||||||
@ -71,23 +38,69 @@
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
mounted() {
|
computed:{
|
||||||
this.api = new ApiService();
|
...mapGetters({
|
||||||
this.getVersion()
|
isAuthenticated: 'auth/isAuthenticated',
|
||||||
|
authStatus: 'auth/authStatus',
|
||||||
|
authRedirectUrl: 'auth/authRedirectUrl',
|
||||||
|
authError: 'auth/error',
|
||||||
|
clientError: 'client/error',
|
||||||
|
serverError: 'server/error',
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
created () {
|
created () {
|
||||||
this.$vuetify.theme.dark = true
|
this.$vuetify.theme.dark = true
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
mounted() {
|
||||||
getVersion() {
|
if (this.$route.query.code && this.$route.query.state) {
|
||||||
this.api.get('/server/version').then((res) => {
|
this.oauth2_exchange({
|
||||||
this.version = res.version;
|
code: this.$route.query.code,
|
||||||
}).catch((e) => {
|
state: this.$route.query.state
|
||||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
})
|
||||||
});
|
} else {
|
||||||
|
this.oauth2_url()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
authError(newValue, oldValue) {
|
||||||
|
console.log(newValue)
|
||||||
|
this.notify('error', newValue);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clientError(newValue, oldValue) {
|
||||||
|
console.log(newValue)
|
||||||
|
this.notify('error', newValue);
|
||||||
|
},
|
||||||
|
|
||||||
|
serverError(newValue, oldValue) {
|
||||||
|
console.log(newValue)
|
||||||
|
this.notify('error', newValue);
|
||||||
|
},
|
||||||
|
|
||||||
|
isAuthenticated(newValue, oldValue) {
|
||||||
|
console.log(`Updating isAuthenticated from ${oldValue} to ${newValue}`);
|
||||||
|
if (newValue === true) {
|
||||||
|
this.$router.push('/clients')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
authStatus(newValue, oldValue) {
|
||||||
|
console.log(`Updating authStatus from ${oldValue} to ${newValue}`);
|
||||||
|
if (newValue === 'redirect') {
|
||||||
|
window.location.replace(this.authRedirectUrl)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
...mapActions('auth', {
|
||||||
|
oauth2_exchange: 'oauth2_exchange',
|
||||||
|
oauth2_url: 'oauth2_url',
|
||||||
|
}),
|
||||||
|
|
||||||
notify(color, msg) {
|
notify(color, msg) {
|
||||||
this.notification.show = true;
|
this.notification.show = true;
|
||||||
this.notification.color = color;
|
this.notification.color = color;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="success"
|
color="success"
|
||||||
@click="startAddClient"
|
@click="startCreate"
|
||||||
>
|
>
|
||||||
Add new client
|
Add new client
|
||||||
<v-icon right dark>mdi-account-multiple-plus-outline</v-icon>
|
<v-icon right dark>mdi-account-multiple-plus-outline</v-icon>
|
||||||
@ -31,15 +31,15 @@
|
|||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-list-item-title class="headline">{{ client.name }}</v-list-item-title>
|
<v-list-item-title class="headline">{{ client.name }}</v-list-item-title>
|
||||||
<v-list-item-subtitle>{{ client.email }}</v-list-item-subtitle>
|
<v-list-item-subtitle>{{ client.email }}</v-list-item-subtitle>
|
||||||
<v-list-item-subtitle>Created: {{ client.created | formatDate }}</v-list-item-subtitle>
|
<v-list-item-subtitle>Created: {{ client.created | formatDate }} by {{ client.createdBy }}</v-list-item-subtitle>
|
||||||
<v-list-item-subtitle>Updated: {{ client.updated | formatDate }}</v-list-item-subtitle>
|
<v-list-item-subtitle>Updated: {{ client.updated | formatDate }} by {{ client.updatedBy }}</v-list-item-subtitle>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
|
|
||||||
<v-list-item-avatar
|
<v-list-item-avatar
|
||||||
tile
|
tile
|
||||||
size="150"
|
size="150"
|
||||||
>
|
>
|
||||||
<v-img :src="`${apiBaseUrl}/client/${client.id}/config?qrcode=true`"/>
|
<v-img :src="'data:image/png;base64, ' + getClientQrcode(client.id)"/>
|
||||||
</v-list-item-avatar>
|
</v-list-item-avatar>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
|
||||||
@ -55,61 +55,61 @@
|
|||||||
</v-chip>
|
</v-chip>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-tooltip bottom>
|
<v-tooltip bottom>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
<v-btn
|
<v-btn
|
||||||
text
|
text
|
||||||
:href="`${apiBaseUrl}/client/${client.id}/config?qrcode=false`"
|
v-on:click="forceFileDownload(client)"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
>
|
>
|
||||||
<span class="d-none d-lg-flex">Download</span>
|
<span class="d-none d-lg-flex">Download</span>
|
||||||
<v-icon right dark>mdi-cloud-download-outline</v-icon>
|
<v-icon right dark>mdi-cloud-download-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<span>Download</span>
|
<span>Download</span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
|
|
||||||
<v-tooltip bottom>
|
<v-tooltip bottom>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
<v-btn
|
<v-btn
|
||||||
text
|
text
|
||||||
@click.stop="startUpdateClient(client)"
|
@click.stop="startUpdate(client)"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
>
|
>
|
||||||
<span class="d-none d-lg-flex">Edit</span>
|
<span class="d-none d-lg-flex">Edit</span>
|
||||||
<v-icon right dark>mdi-square-edit-outline</v-icon>
|
<v-icon right dark>mdi-square-edit-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<span>Edit</span>
|
<span>Edit</span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
|
|
||||||
<v-tooltip bottom>
|
<v-tooltip bottom>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
<v-btn
|
<v-btn
|
||||||
text
|
text
|
||||||
@click="deleteClient(client)"
|
@click="remove(client)"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
>
|
>
|
||||||
<span class="d-none d-lg-flex">Delete</span>
|
<span class="d-none d-lg-flex">Delete</span>
|
||||||
<v-icon right dark>mdi-trash-can-outline</v-icon>
|
<v-icon right dark>mdi-trash-can-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<span>Delete</span>
|
<span>Delete</span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
|
|
||||||
<v-tooltip bottom>
|
<v-tooltip bottom>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
<v-btn
|
<v-btn
|
||||||
text
|
text
|
||||||
@click="sendEmailClient(client.id)"
|
@click="email(client)"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
>
|
>
|
||||||
<span class="d-none d-lg-flex">Send Email</span>
|
<span class="d-none d-lg-flex">Send Email</span>
|
||||||
<v-icon right dark>mdi-email-send-outline</v-icon>
|
<v-icon right dark>mdi-email-send-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<span>Send Email</span>
|
<span>Send Email</span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
<v-spacer/>
|
<v-spacer/>
|
||||||
<v-tooltip right>
|
<v-tooltip right>
|
||||||
<template v-slot:activator="{ on }">
|
<template v-slot:activator="{ on }">
|
||||||
@ -118,7 +118,7 @@
|
|||||||
v-on="on"
|
v-on="on"
|
||||||
color="success"
|
color="success"
|
||||||
v-model="client.enable"
|
v-model="client.enable"
|
||||||
v-on:change="updateClient(client)"
|
v-on:change="update(client)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<span> {{client.enable ? 'Disable' : 'Enable'}} this client</span>
|
<span> {{client.enable ? 'Disable' : 'Enable'}} this client</span>
|
||||||
@ -133,7 +133,7 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
<v-dialog
|
<v-dialog
|
||||||
v-if="client"
|
v-if="client"
|
||||||
v-model="dialogAddClient"
|
v-model="dialogCreate"
|
||||||
max-width="550"
|
max-width="550"
|
||||||
>
|
>
|
||||||
<v-card>
|
<v-card>
|
||||||
@ -210,14 +210,14 @@
|
|||||||
<v-btn
|
<v-btn
|
||||||
:disabled="!valid"
|
:disabled="!valid"
|
||||||
color="success"
|
color="success"
|
||||||
@click="addClient(client)"
|
@click="create(client)"
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
<v-icon right dark>mdi-check-outline</v-icon>
|
<v-icon right dark>mdi-check-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
@click="dialogAddClient = false"
|
@click="dialogCreate = false"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
<v-icon right dark>mdi-close-circle-outline</v-icon>
|
<v-icon right dark>mdi-close-circle-outline</v-icon>
|
||||||
@ -227,7 +227,7 @@
|
|||||||
</v-dialog>
|
</v-dialog>
|
||||||
<v-dialog
|
<v-dialog
|
||||||
v-if="client"
|
v-if="client"
|
||||||
v-model="dialogEditClient"
|
v-model="dialogUpdate"
|
||||||
max-width="550"
|
max-width="550"
|
||||||
>
|
>
|
||||||
<v-card>
|
<v-card>
|
||||||
@ -308,14 +308,14 @@
|
|||||||
<v-btn
|
<v-btn
|
||||||
:disabled="!valid"
|
:disabled="!valid"
|
||||||
color="success"
|
color="success"
|
||||||
@click="updateClient(client)"
|
@click="update(client)"
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
<v-icon right dark>mdi-check-outline</v-icon>
|
<v-icon right dark>mdi-check-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
@click="dialogEditClient = false"
|
@click="dialogUpdate = false"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
<v-icon right dark>mdi-close-circle-outline</v-icon>
|
<v-icon right dark>mdi-close-circle-outline</v-icon>
|
||||||
@ -323,61 +323,50 @@
|
|||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
<Notification v-bind:notification="notification"/>
|
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import {ApiService, API_BASE_URL} from '../services/ApiService'
|
import { mapActions, mapGetters } from 'vuex'
|
||||||
import Notification from '../components/Notification'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Clients',
|
name: 'Clients',
|
||||||
|
|
||||||
components: {
|
|
||||||
Notification
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
api: null,
|
dialogCreate: false,
|
||||||
apiBaseUrl: API_BASE_URL,
|
dialogUpdate: false,
|
||||||
clients: [],
|
|
||||||
notification: {
|
|
||||||
show: false,
|
|
||||||
color: '',
|
|
||||||
text: '',
|
|
||||||
},
|
|
||||||
dialogAddClient: false,
|
|
||||||
dialogEditClient: false,
|
|
||||||
client: null,
|
client: null,
|
||||||
server: null,
|
|
||||||
valid: false,
|
valid: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
computed:{
|
||||||
|
...mapGetters({
|
||||||
|
getClientQrcode: 'client/getClientQrcode',
|
||||||
|
getClientConfig: 'client/getClientConfig',
|
||||||
|
server: 'server/server',
|
||||||
|
clients: 'client/clients',
|
||||||
|
clientQrcodes: 'client/clientQrcodes',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
mounted () {
|
mounted () {
|
||||||
this.api = new ApiService();
|
this.readAllClients()
|
||||||
this.getClients();
|
this.readServer()
|
||||||
this.getServer()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
getClients() {
|
...mapActions('client', {
|
||||||
this.api.get('/client').then((res) => {
|
errorClient: 'error',
|
||||||
this.clients = res
|
readAllClients: 'readAll',
|
||||||
}).catch((e) => {
|
creatClient: 'create',
|
||||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
updateClient: 'update',
|
||||||
});
|
deleteClient: 'delete',
|
||||||
},
|
emailClient: 'email',
|
||||||
|
}),
|
||||||
|
...mapActions('server', {
|
||||||
|
readServer: 'read',
|
||||||
|
}),
|
||||||
|
|
||||||
getServer() {
|
startCreate() {
|
||||||
this.api.get('/server').then((res) => {
|
|
||||||
this.server = res;
|
|
||||||
}).catch((e) => {
|
|
||||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
startAddClient() {
|
|
||||||
this.dialogAddClient = true;
|
|
||||||
this.client = {
|
this.client = {
|
||||||
name: "",
|
name: "",
|
||||||
email: "",
|
email: "",
|
||||||
@ -385,91 +374,87 @@
|
|||||||
allowedIPs: this.server.allowedips,
|
allowedIPs: this.server.allowedips,
|
||||||
address: this.server.address,
|
address: this.server.address,
|
||||||
}
|
}
|
||||||
|
this.dialogCreate = true;
|
||||||
},
|
},
|
||||||
addClient(client) {
|
|
||||||
|
create(client) {
|
||||||
if (client.allowedIPs.length < 1) {
|
if (client.allowedIPs.length < 1) {
|
||||||
this.notify('error', 'Please provide at least one valid CIDR address for client allowed IPs');
|
this.errorClient('Please provide at least one valid CIDR address for client allowed IPs')
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < client.allowedIPs.length; i++){
|
for (let i = 0; i < client.allowedIPs.length; i++){
|
||||||
if (this.$isCidr(client.allowedIPs[i]) === 0) {
|
if (this.$isCidr(client.allowedIPs[i]) === 0) {
|
||||||
this.notify('error', 'Invalid CIDR detected, please correct before submitting');
|
this.errorClient('Invalid CIDR detected, please correct before submitting')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.dialogAddClient = false;
|
this.dialogCreate = false;
|
||||||
|
this.creatClient(client)
|
||||||
this.api.post('/client', client).then((res) => {
|
|
||||||
this.notify('success', `Client ${res.name} successfully added`);
|
|
||||||
this.getClients()
|
|
||||||
}).catch((e) => {
|
|
||||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteClient(client) {
|
remove(client) {
|
||||||
if(confirm(`Do you really want to delete ${client.name} ?`)){
|
if(confirm(`Do you really want to delete ${client.name} ?`)){
|
||||||
this.api.delete(`/client/${client.id}`).then((res) => {
|
this.deleteClient(client)
|
||||||
this.notify('success', "Client successfully deleted");
|
|
||||||
this.getClients()
|
|
||||||
}).catch((e) => {
|
|
||||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
sendEmailClient(id) {
|
email(client) {
|
||||||
this.api.get(`/client/${id}/email`).then((res) => {
|
if (!client.email){
|
||||||
this.notify('success', "Email successfully sent");
|
this.errorClient('Client email is not defined')
|
||||||
this.getClients()
|
return
|
||||||
}).catch((e) => {
|
}
|
||||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
|
||||||
});
|
if(confirm(`Do you really want to send email to ${client.email} with all configurations ?`)){
|
||||||
|
this.emailClient(client)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
startUpdateClient(client) {
|
startUpdate(client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.dialogEditClient = true;
|
this.dialogUpdate = true;
|
||||||
},
|
},
|
||||||
updateClient(client) {
|
|
||||||
|
update(client) {
|
||||||
// check allowed IPs
|
// check allowed IPs
|
||||||
if (client.allowedIPs.length < 1) {
|
if (client.allowedIPs.length < 1) {
|
||||||
this.notify('error', 'Please provide at least one valid CIDR address for client allowed IPs');
|
this.errorClient('Please provide at least one valid CIDR address for client allowed IPs');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < client.allowedIPs.length; i++){
|
for (let i = 0; i < client.allowedIPs.length; i++){
|
||||||
if (this.$isCidr(client.allowedIPs[i]) === 0) {
|
if (this.$isCidr(client.allowedIPs[i]) === 0) {
|
||||||
this.notify('error', 'Invalid CIDR detected, please correct before submitting');
|
this.errorClient('Invalid CIDR detected, please correct before submitting');
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check address
|
// check address
|
||||||
if (client.address.length < 1) {
|
if (client.address.length < 1) {
|
||||||
this.notify('error', 'Please provide at least one valid CIDR address for client');
|
this.errorClient('Please provide at least one valid CIDR address for client');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < client.address.length; i++){
|
for (let i = 0; i < client.address.length; i++){
|
||||||
if (this.$isCidr(client.address[i]) === 0) {
|
if (this.$isCidr(client.address[i]) === 0) {
|
||||||
this.notify('error', 'Invalid CIDR detected, please correct before submitting');
|
this.errorClient('Invalid CIDR detected, please correct before submitting');
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// all good, submit
|
// all good, submit
|
||||||
this.dialogEditClient = false;
|
this.dialogUpdate = false;
|
||||||
|
this.updateClient(client)
|
||||||
this.api.patch(`/client/${client.id}`, client).then((res) => {
|
|
||||||
this.notify('success', `Client ${res.name} successfully updated`);
|
|
||||||
this.getClients()
|
|
||||||
}).catch((e) => {
|
|
||||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
notify(color, msg) {
|
forceFileDownload(client){
|
||||||
this.notification.show = true;
|
let config = this.getClientConfig(client.id)
|
||||||
this.notification.color = color;
|
if (!config) {
|
||||||
this.notification.text = msg;
|
this.errorClient('Failed to download client config');
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
const url = window.URL.createObjectURL(new Blob([config]))
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.setAttribute('download', 'wg0.conf') //or any other extension
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
51
ui/src/components/Footer.vue
Normal file
51
ui/src/components/Footer.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-footer app>
|
||||||
|
<v-row justify="start" no-gutters>
|
||||||
|
<v-col cols="12" lg="6" md="12" sm="12">
|
||||||
|
<div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'left'">
|
||||||
|
<small>Copyright © {{ new Date().getFullYear() }}, Wg Gen Web. </small>
|
||||||
|
<small>This work is licensed under <a class="pr-1 pl-1" href="http://www.wtfpl.net/" target="_blank">WTFPL License.</a></small>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row justify="end" no-gutters>
|
||||||
|
<v-col cols="12" lg="6" md="12" sm="12">
|
||||||
|
<div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'right'">
|
||||||
|
<small>Created with</small>
|
||||||
|
<v-icon class="pr-1 pl-1">mdi-heart</v-icon><span>by</span><a class="pr-2 pl-1" href="mailto:vx3r@127-0-0-1.fr">vx3r</a>
|
||||||
|
<a :href="'https://github.com/vx3r/wg-gen-web/commit/' + version"><kbd>Version: {{ version.substring(0,7) }}</kbd></a>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-footer>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {mapActions, mapGetters} from "vuex";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Footer',
|
||||||
|
|
||||||
|
data: () => ({
|
||||||
|
|
||||||
|
}),
|
||||||
|
|
||||||
|
computed:{
|
||||||
|
...mapGetters({
|
||||||
|
version: 'server/version',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.versionServer()
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
...mapActions('server', {
|
||||||
|
versionServer: 'version',
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
77
ui/src/components/Header.vue
Normal file
77
ui/src/components/Header.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-app-bar app>
|
||||||
|
<img class="mr-3" :src="require('../assets/logo.png')" height="50" alt="Wg Gen Web"/>
|
||||||
|
<v-toolbar-title to="/">Wg Gen Web</v-toolbar-title>
|
||||||
|
|
||||||
|
<v-spacer />
|
||||||
|
|
||||||
|
<v-toolbar-items>
|
||||||
|
<v-btn to="/clients">
|
||||||
|
Clients
|
||||||
|
<v-icon right dark>mdi-account-network-outline</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn to="/server">
|
||||||
|
Server
|
||||||
|
<v-icon right dark>mdi-vpn</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar-items>
|
||||||
|
|
||||||
|
<v-menu
|
||||||
|
left
|
||||||
|
bottom
|
||||||
|
>
|
||||||
|
<template v-slot:activator="{ on }">
|
||||||
|
<v-btn icon v-on="on">
|
||||||
|
<v-icon>mdi-account-circle</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card
|
||||||
|
class="mx-auto"
|
||||||
|
max-width="344"
|
||||||
|
outlined
|
||||||
|
>
|
||||||
|
<v-list-item three-line>
|
||||||
|
<v-list-item-content>
|
||||||
|
<div class="overline mb-4">connected as</div>
|
||||||
|
<v-list-item-title class="headline mb-1">{{user.name}}</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>Email: {{user.email}}</v-list-item-subtitle>
|
||||||
|
<v-list-item-subtitle>Issuer: {{user.issuer}}</v-list-item-subtitle>
|
||||||
|
<v-list-item-subtitle>Issued at: {{ user.issuedAt | formatDate }}</v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
</v-list-item>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn small
|
||||||
|
v-on:click="logout()"
|
||||||
|
>
|
||||||
|
logout
|
||||||
|
<v-icon small right dark>mdi-logout</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
|
||||||
|
</v-app-bar>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {mapActions, mapGetters} from "vuex";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Header',
|
||||||
|
|
||||||
|
computed:{
|
||||||
|
...mapGetters({
|
||||||
|
user: 'auth/user',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
...mapActions('auth', {
|
||||||
|
logout: 'logout',
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -158,7 +158,7 @@
|
|||||||
<v-btn
|
<v-btn
|
||||||
class="ma-2"
|
class="ma-2"
|
||||||
color="success"
|
color="success"
|
||||||
:href="`${apiBaseUrl}/server/config`"
|
v-on:click="forceFileDownload()"
|
||||||
>
|
>
|
||||||
Download server configuration
|
Download server configuration
|
||||||
<v-icon right dark>mdi-cloud-download-outline</v-icon>
|
<v-icon right dark>mdi-cloud-download-outline</v-icon>
|
||||||
@ -167,52 +167,44 @@
|
|||||||
<v-btn
|
<v-btn
|
||||||
class="ma-2"
|
class="ma-2"
|
||||||
color="warning"
|
color="warning"
|
||||||
@click="updateServer"
|
@click="update"
|
||||||
>
|
>
|
||||||
Update server configuration
|
Update server configuration
|
||||||
<v-icon right dark>mdi-update</v-icon>
|
<v-icon right dark>mdi-update</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-divider dark/>
|
<v-divider dark/>
|
||||||
</v-row>
|
</v-row>
|
||||||
<Notification v-bind:notification="notification"/>
|
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import {API_BASE_URL, ApiService} from "../services/ApiService";
|
import {mapActions, mapGetters} from "vuex";
|
||||||
import Notification from '../components/Notification'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Server',
|
name: 'Server',
|
||||||
|
|
||||||
components: {
|
|
||||||
Notification
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
api: null,
|
|
||||||
server: null,
|
|
||||||
apiBaseUrl: API_BASE_URL,
|
|
||||||
notification: {
|
|
||||||
show: false,
|
|
||||||
color: '',
|
|
||||||
text: '',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
computed:{
|
||||||
|
...mapGetters({
|
||||||
|
server: 'server/server',
|
||||||
|
config: 'server/config',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
mounted () {
|
mounted () {
|
||||||
this.api = new ApiService();
|
this.readServer()
|
||||||
this.getServer()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
getServer() {
|
...mapActions('server', {
|
||||||
this.api.get('/server').then((res) => {
|
errorServer: 'error',
|
||||||
this.server = res;
|
readServer: 'read',
|
||||||
}).catch((e) => {
|
updateServer: 'update',
|
||||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
}),
|
||||||
});
|
|
||||||
},
|
update() {
|
||||||
updateServer () {
|
|
||||||
// convert int values
|
// convert int values
|
||||||
this.server.listenPort = parseInt(this.server.listenPort, 10);
|
this.server.listenPort = parseInt(this.server.listenPort, 10);
|
||||||
this.server.persistentKeepalive = parseInt(this.server.persistentKeepalive, 10);
|
this.server.persistentKeepalive = parseInt(this.server.persistentKeepalive, 10);
|
||||||
@ -220,12 +212,12 @@
|
|||||||
|
|
||||||
// check server addresses
|
// check server addresses
|
||||||
if (this.server.address.length < 1) {
|
if (this.server.address.length < 1) {
|
||||||
this.notify('error', 'Please provide at least one valid CIDR address for server interface');
|
this.errorServer('Please provide at least one valid CIDR address for server interface');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < this.server.address.length; i++){
|
for (let i = 0; i < this.server.address.length; i++){
|
||||||
if (this.$isCidr(this.server.address[i]) === 0) {
|
if (this.$isCidr(this.server.address[i]) === 0) {
|
||||||
this.notify('error', `Invalid CIDR detected, please correct ${this.server.address[i]} before submitting`);
|
this.errorServer(`Invalid CIDR detected, please correct ${this.server.address[i]} before submitting`);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,35 +225,34 @@
|
|||||||
// check DNS correct
|
// check DNS correct
|
||||||
for (let i = 0; i < this.server.dns.length; i++){
|
for (let i = 0; i < this.server.dns.length; i++){
|
||||||
if (this.$isCidr(this.server.dns[i] + '/32') === 0) {
|
if (this.$isCidr(this.server.dns[i] + '/32') === 0) {
|
||||||
this.notify('error', `Invalid IP detected, please correct ${this.server.dns[i]} before submitting`);
|
this.errorServer(`Invalid IP detected, please correct ${this.server.dns[i]} before submitting`);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check client AllowedIPs
|
// check client AllowedIPs
|
||||||
if (this.server.allowedips.length < 1) {
|
if (this.server.allowedips.length < 1) {
|
||||||
this.notify('error', 'Please provide at least one valid CIDR address for client allowed IPs');
|
this.errorServer('Please provide at least one valid CIDR address for client allowed IPs');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < this.server.allowedips.length; i++){
|
for (let i = 0; i < this.server.allowedips.length; i++){
|
||||||
if (this.$isCidr(this.server.allowedips[i]) === 0) {
|
if (this.$isCidr(this.server.allowedips[i]) === 0) {
|
||||||
this.notify('error', 'Invalid CIDR detected, please correct before submitting');
|
this.errorServer('Invalid CIDR detected, please correct before submitting');
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.api.patch('/server', this.server).then((res) => {
|
this.updateServer(this.server)
|
||||||
this.notify('success', "Server successfully updated");
|
},
|
||||||
this.server = res;
|
|
||||||
}).catch((e) => {
|
forceFileDownload(){
|
||||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
const url = window.URL.createObjectURL(new Blob([this.config]))
|
||||||
});
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.setAttribute('download', 'wg0.conf') //or any other extension
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
},
|
},
|
||||||
notify(color, msg) {
|
|
||||||
this.notification.show = true;
|
|
||||||
this.notification.color = color;
|
|
||||||
this.notification.text = msg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
import store from './store'
|
||||||
import vuetify from './plugins/vuetify';
|
import vuetify from './plugins/vuetify';
|
||||||
import './plugins/moment';
|
import './plugins/moment';
|
||||||
import './plugins/cidr'
|
import './plugins/cidr'
|
||||||
|
import './plugins/axios'
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
// Don't warn about using the dev version of Vue in development.
|
||||||
|
Vue.config.productionTip = process.env.NODE_ENV === 'production'
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
router,
|
router,
|
||||||
|
store,
|
||||||
vuetify,
|
vuetify,
|
||||||
render: function (h) { return h(App) }
|
render: function (h) { return h(App) }
|
||||||
}).$mount('#app')
|
}).$mount('#app')
|
||||||
|
26
ui/src/plugins/axios.js
Normal file
26
ui/src/plugins/axios.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import axios from "axios";
|
||||||
|
import VueAxios from "vue-axios";
|
||||||
|
import TokenService from "../services/token.service";
|
||||||
|
|
||||||
|
Vue.use(VueAxios, axios);
|
||||||
|
|
||||||
|
let baseUrl = "/api/v1.0";
|
||||||
|
if (process.env.NODE_ENV === "development"){
|
||||||
|
baseUrl = process.env.VUE_APP_API_BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.axios.defaults.baseURL = baseUrl;
|
||||||
|
|
||||||
|
Vue.axios.interceptors.response.use(function (response) {
|
||||||
|
return response;
|
||||||
|
}, function (error) {
|
||||||
|
if (401 === error.response.status) {
|
||||||
|
TokenService.destroyToken();
|
||||||
|
TokenService.destroyClientId();
|
||||||
|
window.location = '/';
|
||||||
|
} else {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -1,22 +1,19 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import VueRouter from 'vue-router'
|
import VueRouter from 'vue-router'
|
||||||
|
import store from "../store";
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'index',
|
|
||||||
component: function () {
|
|
||||||
return import(/* webpackChunkName: "Index" */ '../views/Index.vue')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/clients',
|
path: '/clients',
|
||||||
name: 'clients',
|
name: 'clients',
|
||||||
component: function () {
|
component: function () {
|
||||||
return import(/* webpackChunkName: "Clients" */ '../views/Clients.vue')
|
return import(/* webpackChunkName: "Clients" */ '../views/Clients.vue')
|
||||||
},
|
},
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/server',
|
path: '/server',
|
||||||
@ -24,6 +21,9 @@ const routes = [
|
|||||||
component: function () {
|
component: function () {
|
||||||
return import(/* webpackChunkName: "Server" */ '../views/Server.vue')
|
return import(/* webpackChunkName: "Server" */ '../views/Server.vue')
|
||||||
},
|
},
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -33,4 +33,16 @@ const router = new VueRouter({
|
|||||||
routes
|
routes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if(to.matched.some(record => record.meta.requiresAuth)) {
|
||||||
|
if (store.getters["auth/isAuthenticated"]) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next('/')
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
let baseUrl = "/api/v1.0";
|
|
||||||
if (process.env.NODE_ENV === "development"){
|
|
||||||
baseUrl = process.env.VUE_APP_API_BASE_URL
|
|
||||||
}
|
|
||||||
|
|
||||||
export const API_BASE_URL = baseUrl;
|
|
||||||
|
|
||||||
export class ApiService {
|
|
||||||
get(resource) {
|
|
||||||
return axios
|
|
||||||
.get(`${API_BASE_URL}${resource}`)
|
|
||||||
.then(response => response.data)
|
|
||||||
};
|
|
||||||
|
|
||||||
post(resource, data) {
|
|
||||||
return axios
|
|
||||||
.post(`${API_BASE_URL}${resource}`, data)
|
|
||||||
.then(response => response.data)
|
|
||||||
};
|
|
||||||
|
|
||||||
put(resource, data) {
|
|
||||||
return axios
|
|
||||||
.put(`${API_BASE_URL}${resource}`, data)
|
|
||||||
.then(response => response.data)
|
|
||||||
};
|
|
||||||
|
|
||||||
patch(resource, data) {
|
|
||||||
return axios
|
|
||||||
.patch(`${API_BASE_URL}${resource}`, data)
|
|
||||||
.then(response => response.data)
|
|
||||||
};
|
|
||||||
|
|
||||||
delete(resource) {
|
|
||||||
return axios
|
|
||||||
.delete(`${API_BASE_URL}${resource}`)
|
|
||||||
.then(response => response.data)
|
|
||||||
};
|
|
||||||
}
|
|
59
ui/src/services/api.service.js
Normal file
59
ui/src/services/api.service.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import Vue from "vue";
|
||||||
|
import TokenService from "./token.service";
|
||||||
|
|
||||||
|
const ApiService = {
|
||||||
|
|
||||||
|
setHeader() {
|
||||||
|
Vue.axios.defaults.headers.common.Authorization = `${TokenService.getToken()}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
get(resource) {
|
||||||
|
return Vue.axios.get(resource)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
throw new Error(`ApiService: ${error}`)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
post(resource, params) {
|
||||||
|
return Vue.axios.post(resource, params)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
throw new Error(`ApiService: ${error}`)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
put(resource, params) {
|
||||||
|
return Vue.axios.put(resource, params)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
throw new Error(`ApiService: ${error}`)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
patch(resource, params) {
|
||||||
|
return Vue.axios.patch(resource, params)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
throw new Error(`ApiService: ${error}`)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
delete(resource) {
|
||||||
|
return Vue.axios.delete(resource)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
throw new Error(`ApiService: ${error}`)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getWithConfig(resource, config) {
|
||||||
|
return Vue.axios.get(resource, config)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
throw new Error(`ApiService: ${error}`)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiService;
|
35
ui/src/services/token.service.js
Normal file
35
ui/src/services/token.service.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const TOKEN_KEY = "token";
|
||||||
|
const CLIENT_ID_KEY = "client_id";
|
||||||
|
|
||||||
|
export const getToken = () => {
|
||||||
|
return window.localStorage.getItem(TOKEN_KEY);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveToken = token => {
|
||||||
|
window.localStorage.setItem(TOKEN_KEY, token);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const destroyToken = () => {
|
||||||
|
window.localStorage.removeItem(TOKEN_KEY);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getClientId = () => {
|
||||||
|
return window.localStorage.getItem(CLIENT_ID_KEY);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveClientId = token => {
|
||||||
|
window.localStorage.setItem(CLIENT_ID_KEY, token);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const destroyClientId = () => {
|
||||||
|
window.localStorage.removeItem(CLIENT_ID_KEY);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getToken,
|
||||||
|
saveToken,
|
||||||
|
destroyToken,
|
||||||
|
getClientId,
|
||||||
|
saveClientId,
|
||||||
|
destroyClientId
|
||||||
|
};
|
19
ui/src/store/index.js
Normal file
19
ui/src/store/index.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
import auth from "./modules/auth";
|
||||||
|
import client from "./modules/client";
|
||||||
|
import server from "./modules/server";
|
||||||
|
|
||||||
|
Vue.use(Vuex)
|
||||||
|
|
||||||
|
export default new Vuex.Store({
|
||||||
|
state: {},
|
||||||
|
getters : {},
|
||||||
|
mutations: {},
|
||||||
|
actions:{},
|
||||||
|
modules: {
|
||||||
|
auth,
|
||||||
|
client,
|
||||||
|
server
|
||||||
|
}
|
||||||
|
})
|
126
ui/src/store/modules/auth.js
Normal file
126
ui/src/store/modules/auth.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import ApiService from "../../services/api.service";
|
||||||
|
import TokenService from "../../services/token.service";
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
error: null,
|
||||||
|
user: null,
|
||||||
|
authStatus: '',
|
||||||
|
authRedirectUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
error(state) {
|
||||||
|
return state.error;
|
||||||
|
},
|
||||||
|
user(state) {
|
||||||
|
return state.user;
|
||||||
|
},
|
||||||
|
isAuthenticated(state) {
|
||||||
|
return state.user !== null;
|
||||||
|
},
|
||||||
|
authRedirectUrl(state) {
|
||||||
|
return state.authRedirectUrl
|
||||||
|
},
|
||||||
|
authStatus(state) {
|
||||||
|
return state.authStatus
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
user({ commit }){
|
||||||
|
ApiService.get("/auth/user")
|
||||||
|
.then( resp => {
|
||||||
|
commit('user', resp)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err);
|
||||||
|
commit('logout')
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
oauth2_url({ commit, dispatch }){
|
||||||
|
if (TokenService.getToken()) {
|
||||||
|
ApiService.setHeader();
|
||||||
|
dispatch('user');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ApiService.get("/auth/oauth2_url")
|
||||||
|
.then(resp => {
|
||||||
|
if (resp.codeUrl === '_magic_string_fake_auth_no_redirect_'){
|
||||||
|
console.log("server report oauth2 is disabled, fake exchange")
|
||||||
|
commit('authStatus', 'disabled')
|
||||||
|
TokenService.saveClientId(resp.clientId)
|
||||||
|
dispatch('oauth2_exchange', {code: "", state: resp.state})
|
||||||
|
} else {
|
||||||
|
commit('authStatus', 'redirect')
|
||||||
|
commit('authRedirectUrl', resp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('authStatus', 'error')
|
||||||
|
commit('error', err);
|
||||||
|
commit('logout')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
oauth2_exchange({ commit, dispatch }, data){
|
||||||
|
data.clientId = TokenService.getClientId()
|
||||||
|
ApiService.post("/auth/oauth2_exchange", data)
|
||||||
|
.then(resp => {
|
||||||
|
commit('authStatus', 'success')
|
||||||
|
commit('token', resp)
|
||||||
|
dispatch('user');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('authStatus', 'error')
|
||||||
|
commit('error', err);
|
||||||
|
commit('logout')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
logout({ commit }){
|
||||||
|
ApiService.get("/auth/logout")
|
||||||
|
.then(resp => {
|
||||||
|
commit('logout')
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('authStatus', '')
|
||||||
|
commit('error', err);
|
||||||
|
commit('logout')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
error(state, error) {
|
||||||
|
state.error = error;
|
||||||
|
},
|
||||||
|
authStatus(state, authStatus) {
|
||||||
|
state.authStatus = authStatus;
|
||||||
|
},
|
||||||
|
authRedirectUrl(state, resp) {
|
||||||
|
state.authRedirectUrl = resp.codeUrl;
|
||||||
|
TokenService.saveClientId(resp.clientId);
|
||||||
|
},
|
||||||
|
token(state, token) {
|
||||||
|
TokenService.saveToken(token);
|
||||||
|
ApiService.setHeader();
|
||||||
|
TokenService.destroyClientId();
|
||||||
|
},
|
||||||
|
user(state, user) {
|
||||||
|
state.user = user;
|
||||||
|
},
|
||||||
|
logout(state) {
|
||||||
|
state.user = null;
|
||||||
|
TokenService.destroyToken();
|
||||||
|
TokenService.destroyClientId();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations
|
||||||
|
}
|
177
ui/src/store/modules/client.js
Normal file
177
ui/src/store/modules/client.js
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
import ApiService from "../../services/api.service";
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
error: null,
|
||||||
|
clients: [],
|
||||||
|
clientQrcodes: [],
|
||||||
|
clientConfigs: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
error(state) {
|
||||||
|
return state.error;
|
||||||
|
},
|
||||||
|
clients(state) {
|
||||||
|
return state.clients;
|
||||||
|
},
|
||||||
|
getClientQrcode: (state) => (id) => {
|
||||||
|
let item = state.clientQrcodes.find(item => item.id === id)
|
||||||
|
// initial load fails, must wait promise and stuff...
|
||||||
|
return item ? item.qrcode : "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
|
||||||
|
},
|
||||||
|
getClientConfig: (state) => (id) => {
|
||||||
|
let item = state.clientConfigs.find(item => item.id === id)
|
||||||
|
return item ? item.config : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
error({ commit }, error){
|
||||||
|
commit('error', error)
|
||||||
|
},
|
||||||
|
|
||||||
|
readAll({ commit, dispatch }){
|
||||||
|
ApiService.get("/client")
|
||||||
|
.then(resp => {
|
||||||
|
commit('clients', resp)
|
||||||
|
dispatch('readQrcodes')
|
||||||
|
dispatch('readConfigs')
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
create({ commit, dispatch }, client){
|
||||||
|
ApiService.post("/client", client)
|
||||||
|
.then(resp => {
|
||||||
|
dispatch('readQrcode', resp)
|
||||||
|
dispatch('readConfig', resp)
|
||||||
|
commit('create', resp)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
update({ commit, dispatch }, client){
|
||||||
|
ApiService.patch(`/client/${client.id}`, client)
|
||||||
|
.then(resp => {
|
||||||
|
dispatch('readQrcode', resp)
|
||||||
|
dispatch('readConfig', resp)
|
||||||
|
commit('update', resp)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
delete({ commit }, client){
|
||||||
|
ApiService.delete(`/client/${client.id}`)
|
||||||
|
.then(() => {
|
||||||
|
commit('delete', client)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
email({ commit }, client){
|
||||||
|
ApiService.get(`/client/${client.id}/email`)
|
||||||
|
.then(() => {
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
readQrcode({ state, commit }, client){
|
||||||
|
ApiService.getWithConfig(`/client/${client.id}/config?qrcode=true`, {responseType: 'arraybuffer'})
|
||||||
|
.then(resp => {
|
||||||
|
let image = Buffer.from(resp, 'binary').toString('base64')
|
||||||
|
commit('clientQrcodes', { client, image })
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
readConfig({ state, commit }, client){
|
||||||
|
ApiService.getWithConfig(`/client/${client.id}/config?qrcode=false`, {responseType: 'arraybuffer'})
|
||||||
|
.then(resp => {
|
||||||
|
commit('clientConfigs', { client: client, config: resp })
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
readQrcodes({ state, dispatch }){
|
||||||
|
state.clients.forEach(client => {
|
||||||
|
dispatch('readQrcode', client)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
readConfigs({ state, dispatch }){
|
||||||
|
state.clients.forEach(client => {
|
||||||
|
dispatch('readConfig', client)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
error(state, error) {
|
||||||
|
state.error = error;
|
||||||
|
},
|
||||||
|
clients(state, clients){
|
||||||
|
state.clients = clients
|
||||||
|
},
|
||||||
|
create(state, client){
|
||||||
|
state.clients.push(client)
|
||||||
|
},
|
||||||
|
update(state, client){
|
||||||
|
let index = state.clients.findIndex(x => x.id === client.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.clients.splice(index, 1);
|
||||||
|
state.clients.push(client);
|
||||||
|
} else {
|
||||||
|
state.error = "update client failed, not in list"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete(state, client){
|
||||||
|
let index = state.clients.findIndex(x => x.id === client.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.clients.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
state.error = "delete client failed, not in list"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientQrcodes(state, { client, image }){
|
||||||
|
let index = state.clientQrcodes.findIndex(x => x.id === client.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.clientQrcodes.splice(index, 1);
|
||||||
|
}
|
||||||
|
state.clientQrcodes.push({
|
||||||
|
id: client.id,
|
||||||
|
qrcode: image
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clientConfigs(state, { client, config }){
|
||||||
|
let index = state.clientConfigs.findIndex(x => x.id === client.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
state.clientConfigs.splice(index, 1);
|
||||||
|
}
|
||||||
|
state.clientConfigs.push({
|
||||||
|
id: client.id,
|
||||||
|
config: config
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations
|
||||||
|
}
|
100
ui/src/store/modules/server.js
Normal file
100
ui/src/store/modules/server.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import ApiService from "../../services/api.service";
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
error: null,
|
||||||
|
server: null,
|
||||||
|
config: '',
|
||||||
|
version: '_ci_build_not_run_properly_',
|
||||||
|
}
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
error(state) {
|
||||||
|
return state.error;
|
||||||
|
},
|
||||||
|
|
||||||
|
server(state) {
|
||||||
|
return state.server;
|
||||||
|
},
|
||||||
|
|
||||||
|
version(state) {
|
||||||
|
return state.version;
|
||||||
|
},
|
||||||
|
|
||||||
|
config(state) {
|
||||||
|
return state.config;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
error({ commit }, error){
|
||||||
|
commit('error', error)
|
||||||
|
},
|
||||||
|
|
||||||
|
read({ commit, dispatch }){
|
||||||
|
ApiService.get("/server")
|
||||||
|
.then(resp => {
|
||||||
|
commit('server', resp)
|
||||||
|
dispatch('config')
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
update({ commit }, server){
|
||||||
|
ApiService.patch(`/server`, server)
|
||||||
|
.then(resp => {
|
||||||
|
commit('server', resp)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
config({ commit }){
|
||||||
|
ApiService.getWithConfig("/server/config", {responseType: 'arraybuffer'})
|
||||||
|
.then(resp => {
|
||||||
|
commit('config', resp)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
version({ commit }){
|
||||||
|
ApiService.get("/server/version")
|
||||||
|
.then(resp => {
|
||||||
|
commit('version', resp.version)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
commit('error', err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
error(state, error) {
|
||||||
|
state.error = error;
|
||||||
|
},
|
||||||
|
|
||||||
|
server(state, server){
|
||||||
|
state.server = server
|
||||||
|
},
|
||||||
|
|
||||||
|
config(state, config){
|
||||||
|
state.config = config
|
||||||
|
},
|
||||||
|
|
||||||
|
version(state, version){
|
||||||
|
state.version = version
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
<template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
created () {
|
|
||||||
this.$router.replace({ name: 'clients' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
30
util/util.go
30
util/util.go
@ -1,6 +1,8 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
@ -9,6 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
AuthTokenHeaderName = "Authorization"
|
||||||
// RegexpEmail check valid email
|
// RegexpEmail check valid email
|
||||||
RegexpEmail = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
RegexpEmail = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||||
)
|
)
|
||||||
@ -51,7 +54,7 @@ func DirectoryExists(name string) bool {
|
|||||||
return info.IsDir()
|
return info.IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAvailableIp search for an available in cidr against a list of reserved ips
|
// GetAvailableIp search for an available ip in cidr against a list of reserved ips
|
||||||
func GetAvailableIp(cidr string, reserved []string) (string, error) {
|
func GetAvailableIp(cidr string, reserved []string) (string, error) {
|
||||||
ip, ipnet, err := net.ParseCIDR(cidr)
|
ip, ipnet, err := net.ParseCIDR(cidr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -132,3 +135,28 @@ func BroadcastAddr(n *net.IPNet) net.IP {
|
|||||||
}
|
}
|
||||||
return broadcast
|
return broadcast
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateRandomBytes returns securely generated random bytes.
|
||||||
|
// It will return an error if the system's secure random
|
||||||
|
// number generator fails to function correctly, in which
|
||||||
|
// case the caller should not continue.
|
||||||
|
func GenerateRandomBytes(n int) ([]byte, error) {
|
||||||
|
b := make([]byte, n)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
// Note that err == nil only if we read len(b) bytes.
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRandomString returns a URL-safe, base64 encoded
|
||||||
|
// securely generated random string.
|
||||||
|
// It will return an error if the system's secure random
|
||||||
|
// number generator fails to function correctly, in which
|
||||||
|
// case the caller should not continue.
|
||||||
|
func GenerateRandomString(s int) (string, error) {
|
||||||
|
b, err := GenerateRandomBytes(s)
|
||||||
|
return base64.URLEncoding.EncodeToString(b), err
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user