0
0
mirror of https://github.com/vx3r/wg-gen-web.git synced 2025-09-11 12:24:27 +00:00

oauth2 oidc, vuex store

This commit is contained in:
vx3r
2020-04-28 20:11:49 +09:00
parent f90124afbf
commit 125ddaef0f
36 changed files with 2050 additions and 847 deletions

View File

@ -2,244 +2,12 @@ package api
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/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"
"gitlab.127-0-0-1.fr/vx3r/wg-gen-web/api/v1"
)
// ApplyRoutes applies router to gin Router
func ApplyRoutes(r *gin.Engine) {
client := r.Group("/api/v1.0/client")
func ApplyRoutes(r *gin.Engine, private bool) {
api := r.Group("/api")
{
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)
apiv1.ApplyRoutes(api, private)
}
}
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
View 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
View 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
View 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
View 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)
}
}
}